Java之线程安全
什么是线程安全
Java线程的两个特性 ,可见性和有序性。
- 线程之间不能直接传递数据进行交互,只能通过变量来实现数据交互。而当一个对象在多个工作内存中都存在它的复制体时,如果有一个方式使主内存中的这个变量改变时,那么其他所有的线程也得看到修改后的值,这就是可见性。
- 那么什么是有序性呢? 拿银行出来说事,例如:一张银行卡里有一百块钱,甲去银行取100块钱,乙同时也去银行取100块钱,当他们俩同时取钱时,那甲取到100,乙取到100,那银行不久亏了100嘛,所以对于这种bug,就必须得设定当甲取钱时,其他人就不得操作银行卡,这便是有序性。
线程安全:如果线程执行过程中不会产生共享资源的冲突就是线程安全的。
当多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,锁机制就会锁住其他线程,于是其他线程就不能进行访问直到该线程访问或操作完,其他线程才可使用。
解决线程不安全问题的几个方法
- 局部变量不存在线程安全问题,因为每个线程进入的方法会有单独的空间。
- 加上synchronized关键字同步,可以同步整个方法,也可以同步部分代码块。
- 使用Lock对象(tryLock,lock,unlock)
- 使用读写锁,提升性能,可以一起读,但读的时候不能写,写的时候不能读和写。
- 使用本地线程ThreadLocal,为每个线程保存一份单独的空间,该方法相当于使用空间换时间。
synchronized的使用
// 输出类
public class Printer {
boolean start = true;
public synchronized void printernum() throws InterruptedException {
for (int i = 1; i <=52 ; i++) {
System.out.print(i);
if(i%2==0){
start=false;
this.notify();
this.wait();
}
// Thread.sleep(500);
}
}
public synchronized void printerword() throws InterruptedException { //加上了synchronized关键字便加上了锁
while (start) this.wait();
for (int i =65; i <=90 ; i++) {
System.out.print((char) i);
this.notify();
if(i!=90)this.wait();
// Thread.sleep(500);
}
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Printer printer = new Printer();
Runnable r1 = () ->{
try {
printer.printernum();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable r2 = () ->{
try {
printer.printerword();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(r1).start();
new Thread(r2).start();
}
}
synchronized称为互斥同步锁,也叫阻塞同步锁,能对没有获取锁的线程阻塞。
Java的互斥同步锁是synchronized和ReentrantLock。
前者是语言级别实现的锁,后者是API层面的锁;synchronized是jvm实现的,遇到异常就会释放锁,但是ReentrantLock必须由程序主动释放锁,比如程序必须要在finally中关闭锁,比synchronized更灵活。
互斥同步锁都是可重入锁,它保证不会造成死锁,但很消耗性能。
- synchronized可以锁局部代码块。
- synchronized写在实例方法上,默认的锁对象为this,当一个对象的实例方法使用了synchronized,那么这个对象的其他synchronized方法也同样加了this锁,其他线程也同样需要等待锁释放才能运行这些synchronized方法。
- synchronized写在静态方法上,默认的锁对象为该方法的类的字节码,其他使用该字节码同步锁的方法,需要等待该字节码锁被释放。
Lock对象的使用
// 输出类
public class PrinterName {
Lock lock = new ReentrantLock();
public void sayname(){
try {
if(lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println("name.. start.." + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("name的else" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sayname2() {
try {
// 尝试获取锁,获取不到等待5S. 5S后没获取到就返回false
if (lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println("name22222222.. start.." + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("name2222的else" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 同步锁线程类
public class MysynchronzedThread implements Runnable{
PrinterName printerName;
public MysynchronzedThread(PrinterName printerName){
this.printerName =printerName;
}
@Override
public void run() {
while (true){
if((((int)(Math.random() * 100)) % 2) == 0){
printerName.sayname();
}else{
printerName.sayname2();
}
}
}
}
// 测试类
public class Test {
public static void main(String[] args) {
PrinterName printerName = new PrinterName();
MysynchronzedThread thread1 = new MysynchronzedThread(printerName);
MysynchronzedThread thread2 = new MysynchronzedThread(printerName);
Thread threadA = new Thread(thread1,"A");
Thread threadB = new Thread(thread2,"B");
//threadA.setPriority(Thread.MAX_PRIORITY - 1);
//threadB.setPriority(Thread.MAX_PRIORITY);
threadA.start();
threadB.start();
}
}
读写锁的使用
// 读写锁线程类
public class MyReadwriterThread extends Thread{
private ReadWriter readWriter;
public MyReadwriterThread(String threadName, ReadWriter readWriter){
super(threadName);
this.readWriter =readWriter;
}
public void run(){
while (true){
int num = (int) (Math.random()*100);
if(num%2==0) readWriter.readName();
else readWriter.writerName();
}
}
}
// 方法
public class ReadWriter {
ReadWriteLock lock = new ReentrantReadWriteLock();
String name;
public void readName(){
// 获取这个锁的读锁
Lock readlock = this.lock.readLock();
readlock.lock();
System.out.println(Thread.currentThread().getName() + "进入read:" + name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "准备结束read:" + name);
// 释放读锁
readlock.unlock();
}
public void writerName(){
Lock writeLock = this.lock.writeLock();
writeLock.lock();
String randomname = (char)((int)(Math.random()*100))+" ";
System.out.println(Thread.currentThread().getName() + "正在赋值..准备赋值为:" + randomname);
this.name = randomname;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "赋值完毕,结果为:" + name);
writeLock.unlock();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
ReadWriter readWriter = new ReadWriter();
MyReadwriterThread thread1 = new MyReadwriterThread("a线程",readWriter);
MyReadwriterThread thread2 = new MyReadwriterThread("b线程",readWriter);
thread1.start();
thread2.start();
}
}
本地线程ThreadLocal的使用
// 本地线程类
public class MyprinterThread implements Runnable{
//本地线程,作用于死锁的解决
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private String username;
private Printer printer;
public MyprinterThread(String username, Printer printer) {
this.username = username;
this.printer = printer;
}
public void run(){
for (int i = 0; i < 100; i++) {
if(threadLocal.get()==null){
threadLocal.set(username);
}
printer.printerName();
}
}
}
// 输出方法 的类
public class Printer {
String name;
int age;
int gender;
public void printerName(){
File file = new File("d:"+File.separator+name+".txt");
try(OutputStream os = new FileOutputStream(file,true)){
if(!file.exists()) file.createNewFile();
for (int i = 0; i < name.length(); i++) {
os.write(name.charAt(i));
}
os.write("\r\n".getBytes() );
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 测试类 在文件中添加数据
public class TestPrinter {
public static Integer num = 10;
public static void main(String[] args) {
Printer printer = new Printer();
MyprinterThread a = new MyprinterThread("aaaaaaaaaaaaaaaaa", printer);
MyprinterThread b= new MyprinterThread("bbbbbbbbbbbbbbbbbbb",printer );
new Thread(a).start();
new Thread(b).start();
}
}
什么是死锁? 怎么解决死锁?
同步(synchronization)是指当一个线程访问数据时,其它线程就不能对同一个数据进行访问,即同一时刻只能有一个线程访问该数据。
同步最常见的方式就是使用锁,也就是线程锁。锁是一种非强制机制,每一个线程在访问数据或资源之前,首先试图获取锁,并在访问结束之后释放锁。在锁被占用时试图获取锁,线程会进入等待状态,直到锁被释放再次变为可用。
死锁就是当两个或多个线程直接相互等待彼此释放锁时,就会造成死锁。
死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
解决方法 :
1.使用Lock的tryLock,一段时间获取不到就执行unlock释放
2.synchronized使用同一个锁对象
3.使用ThreadLocal
上一篇: throw 与throws
下一篇: throws与throw