线程中并发指一个时间段中多个线程都处于已启动但没有运行结束的状态。多个线程之间默认并发运行,这种运行方式往往会出现交叉的情况。使原本并发运行的多个线程实现串行运行,即多线程间同步执行,需要通过对象锁机制来实现,synchronized就是一个利用锁实现线程同步的关键字。
public class ShareDataTest {
public static void main(String[] args) {
Object object = new Object();
new CounterThread("~~~~~~~~~~~~~~~~",object).start();
new CounterThread("@@@@@",object).start();
//两个线程默认状态下抢占执行,会导致交叉执行,要使一个线程彻底执行完在执行另一个,先后顺序任意,用数据共享,共享的数据可以是任意类型
}
}
class CounterThread extends Thread{
Object object;
CounterThread(String name,Object obj){
super(name);
this.object = obj;
}
@Override
public void run() {
synchronized (String.class) {//写Object【定义的共享数据,若是定义在线程中的静态数据,在同类线程中可以实现,静态数据也是共享的】,""【常量池常量】,XXX.class【.class返回的是一个唯一的Class对象,单例,在多个线程中使用操作的是同一个】可以实现;写this【若this指代抢占cpu的多个线程中使用的同一个线程对象则可以】或者把synchronized加到run方法前【都代表当前线程对象】,无法实现
for(int i=0;i<50;i++){
System.out.println(getName()+i);
}
}//synchronized框起来的代码块叫同步代码块,原理是锁,在多线程操作相同数据时加锁,访问结束后释放,所以强调,必须是共享的数据
}
}
多线程同步原理:
1.被synchronized括着的部分就是线程执行临界区,每次仅能有一个线程执行该临界区中的代码:当多个线程中的某个线程先拿到对象锁, 则该线程执行临界区内的代码,其他线程只能在临界区外部等待,当此线程执行完临界区中的代码后,在临界区外部等待的其他线程开始再次竞争以获取对象锁,进而执行临界区中的代码,但只有一条线程“胜利”。
2.临界区中的代码具有互斥性、唯一性和排它性:一个线程只有执行完临界区中的代码另一个线程才能执行。
synchronized使用方法:
1.声明同步方法:若为非静态同步方法,则多线程间共享调用该方法的对象;若为静态同步方法,则多线程之间同享调用该方法的类;
public synchronized void methodName( ){
//同步操作方法体
}
2.同步代码块:
synchronized (需要同步操作的对象) {
//同步对象操作的语句
}
在静态方法中同步对象不能使用this,此时括号中用”类名.class“:此时多个线程之间共享该类对应的Class类对象。
class Bike{
public static void move(String personName){
String threadName = Thread.currentThread().getName();
System.out.println(“当前线程线程:”+threadName);//判断当前那条线程在执行该方法
synchronized(Bike.class){
for (int i = 1; i <= 3; i++) {
System.out.println(personName+":已经运行"+i+"秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程协作:
synchronized关键字只是起到了多个线程“串行”执行临界区中代码的作用,但是哪个线程先执行,哪个线程后执行依无法确定,Object类中的wait()、notify()和notifyAll()三个方法解决了线程间的协作问题,通过这三个方法的“合理”使用可以确定多线程中线程的先后执行顺序:
1.wait():对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。
2.notify():对象锁调用notify()方法就会唤醒在此对象锁上等待的单个线程。
3.notifyAll():对象锁调用notifyAll()方法就会唤醒在此对象锁上等待的所有线程;调用notifyAll()方法并不会立即激活某个等待线程,它只能撤销等待线程的中断状态,这样它们就能够在当前线程退出同步方法或同步代码块后与其它线程展开竞争,以争取获得资源对象来执行。
使用Object类中的wait()、notify()和notifyAll()三个方法需要注意以下几点:
1.wait()方法需要和notify()或notifyAll()方法中的一个配对使用,且wait方法与notify()或notifyAll()方法配对使用时不能在同一个线程中。
2.wait()方法、notify()方法和notifyAll()方法必须在同步方法或者同步代码块中使用,否则出现IllegalMonitorStateException 异常。
3.调用wait()方法、notify()方法和notifyAll()方法的对象必须和同步锁对象是一个对象。
public class CooperationTest {
private static String time;
public static void main(String[] args) {
final Object OBJECT = new Object();
new TimeThread(OBJECT).start();
new Display(OBJECT).start();
}
//显示时间的线程
static class Display extends Thread{
Object object;
Display(Object object){
this.object = object;
}
@Override
public void run() {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);
}
}
//获取时间的线程
static class TimeThread extends Thread{
Object object;
TimeThread(Object object){
this.object = object;
}
@Override
public void run() {
time = new Date().toString();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
object.notify();
}
}
}
}
sleep()方法和wait()方法区别:
sleep()方法被调用后当前线程进入阻塞状态,但是当前线程仍然持有对象锁,在当前线程sleep期间,其它线程无法执行sleep方法所在临界区中的代码。
对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,在当前线程处于线程等待期间,其它线程可以执行wait方法所在临界区中的代码。