线程基础、线程之间的共享和协作
(目前会将一些概念简单描述,一些重点的点会详细描述)
线程常用方法和线程的状态
start():调用start()方法后,使线程从新建状态处于就绪状态。
sleep():调用sleep()方法后,设置休眠时间,使线程从运行状态处于阻塞(休眠)状态,休眠时间到,线程从阻塞状态转变为就绪状态。
wait():调用wait()方法后,使线程从运行状态处于阻塞(休眠)状态,只有通过notify()或者notifyAll()方法重新使线程处于就绪状态。(后续补充notify()和notifyAll()方法)。
interrupt():调用interrupt()方法后,不是强制关闭线程,只是跟线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
isInterrypt():线程中断标志位,true/false两个Boolean值,用来判断是否调用interrupt()方法,告诉线程是否中断。
interrupted():判断线程是否处于中断状态,并将中断标志位改为false。
run():运行线程的方法。
synchronized内置锁
1、用处
synchronized作为线程同步的关键字,设计到锁的概念,下面就对锁的概念进行详细介绍。
Java内置锁是一个互斥锁,这就说明最多只有一个线程能够获得该锁,例如两个线程:线程A和线程B,如果线程A尝试去获得线程B的内置锁,则线程A必须等待或者阻塞,直到线程B释放这个锁为止;如果线程B永不释放这个锁,则线程A则永远处于等待或阻塞状态。
Java的对象锁和类锁在锁的概念上,与内置锁几乎是一致的,但是对象锁和类锁的区别是非常大的。
2、对象锁
用synchronized修饰非静态方法、用synchronized(this)作为同步代码块、用synchronized(非this对象)的用法锁的是对象,线程想要执行对应的同步代码,需要先获得对象锁。
3、类锁
用synchronized修饰静态方法、用synchronized(类.class)的用法锁的是类,线程想要执行对应的同步代码,需要先获得类锁。
以下对synchronized关键字的用法,对象锁,类锁用实际代码为例子进行介绍:
1、先看一个非线程安全的实例,看看synchronized的用途
public class SynRun {
public static void main(String[] args) {
// 定义HasSelfNum对象
HasSelfNum hasSelfNum = new HasSelfNum();
// 定义ThreadZS多线程类
ThreadZS threadZS = new ThreadZS(hasSelfNum);
threadZS.start();
// 定义ThreadLS多线程类
ThreadLS threadLS = new ThreadLS(hasSelfNum);
threadLS.start();
}
}
// 定义一个类HasSelfNum,作为同步设置num的变化
class HasSelfNum {
// 定义一个变量num
private int num = 0;
// 定义一个类的方法addNum,并传一个字符串作为形参
public void addNum(String name) {
try {
if (name.equals("zs")) {
num = 100;
System.out.println("zs设置了num参数...");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("ls设置了num参数...");
}
// 将num参数输出
System.out.println(name + " 设置的 num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 定义一个多线程类ThreadZS
class ThreadZS extends Thread {
// 定义HasSelfNum
private HasSelfNum hasSelfNum;
// 设置构造函数,将对象赋值
public ThreadZS(HasSelfNum hasSelfNum) {
super();
this.hasSelfNum = hasSelfNum;
}
// 定义run方法
@Override
public void run() {
super.run();
hasSelfNum.addNum("zs");
}
}
// 定义一个多线程类ThreadLS
class ThreadLS extends Thread {
// 定义HasSelfNum
private HasSelfNum hasSelfNum;
// 设置构造函数,将对象赋值
public ThreadLS(HasSelfNum hasSelfNum) {
super();
this.hasSelfNum = hasSelfNum;
}
// 定义run方法
@Override
public void run() {
super.run();
hasSelfNum.addNum("ls");
}
}
控制台输出结果:
zs设置了num参数...
ls设置了num参数...
ls 设置的 num = 200
zs 设置的 num = 200
看到输出结果是非线程安全的。
接下来将HasSelfNum类里面的addNum()方法加上synchronized关键字,其余代码不变
// 定义一个类HasSelfNum,作为同步设置num的变化
class HasSelfNum {
// 定义一个变量num
private int num = 0;
// 定义一个类的方法addNum,并传一个字符串作为形参,加上了synchronized关键字
public synchronized void addNum(String name) {
try {
if (name.equals("zs")) {
num = 100;
System.out.println("zs设置了num参数...");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("ls设置了num参数...");
}
// 将num参数输出
System.out.println(name + " 设置的 num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行后,控制台输出结果:
zs设置了num参数...
zs 设置的 num = 100
ls设置了num参数...
ls 设置的 num = 200
由此可看出,两个线程访问同一个对象中的同步方法一定是线程安全的。因为对象的方法加上了synchronized关键字成为同步方法,所以先把zs打印出来,再把ls打印出来。
2、不同线程调用不同对象
只修改了main方法里面的写法,其余代码不变
public static void main(String[] args) {
// 定义2个HasSelfNum对象
HasSelfNum hasSelfNum1 = new HasSelfNum();
HasSelfNum hasSelfNum2 = new HasSelfNum();
// 定义ThreadZS多线程类,用hasSelfNum1对象的方法
ThreadZS threadZS = new ThreadZS(hasSelfNum1);
threadZS.start();
// 定义ThreadLS多线程类,用hasSelfNum2对象的方法
ThreadLS threadLS = new ThreadLS(hasSelfNum2);
threadLS.start();
}
控制台输出结果:
zs设置了num参数...
ls设置了num参数...
ls 设置的 num = 200
zs 设置的 num = 100
可以看出输出结果是非同步的,因为线程threadZS获得的是hasSelfNum1的对象锁,threadLS获得的是hasSelfNum2的对象锁,他们没有获得同一个对象锁,没有出现竞争情况,因此是非同步的结果
3、运用synchronized(tihs)同步代码块
public class SynRun1 {
public static void main(String[] args) {
// 初始化ShowTime对象
ShowTime showTime = new ShowTime();
// 初始化ThreadA多线层对象
ThreadA threadA = new ThreadA(showTime);
threadA.start();
// 初始化TreadB多线程对象
ThreadB threadB = new ThreadB(showTime);
threadB.start();
}
}
// 定义一个类ShowTime
class ShowTime {
// 定义一个显式时间方法showTime
public void showTime() {
// 利用synchronized(this)同步代码块作为同步方法
try {
synchronized (this) {
System.out.println("一个进程开始运行,运行时间为:" + System.currentTimeMillis());
// 设置休眠时间
Thread.sleep(2000);
System.out.println("这个进程运行结束,结束时间为:" + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 定义一个线程A
class ThreadA extends Thread {
// 定义ShowTime对象
private ShowTime showTime;
// 定义有餐构造函数
public ThreadA(ShowTime showTime) {
super();
this.showTime = showTime;
}
// 定义run方法
@Override
public void run() {
super.run();
showTime.showTime();
}
}
// 定义一个线程B
class ThreadB extends Thread {
// 定义ShowTime对象
private ShowTime showTime;
// 定义有餐构造函数
public ThreadB(ShowTime showTime) {
super();
this.showTime = showTime;
}
// 定义run方法
@Override
public void run() {
super.run();
showTime.showTime();
}
}
控制台输出结果为:
一个进程开始运行,运行时间为:1539525103617
这个进程运行结束,结束时间为:1539525105619
一个进程开始运行,运行时间为:1539525105619
这个进程运行结束,结束时间为:1539525107619
结果显示也是同步的,线程获取的是synchronized(this){}括号里面的对象实例的对象锁。
4、运用synchronized(非this对象)
public class SynRun1 {
public static void main(String[] args) {
ShowInfo service = new ShowInfo("Cansluck");
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
class ShowInfo {
String info = new String();
public ShowInfo(String info) {
this.info = info;
}
public void showInfo() {
try {
synchronized (info) {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private ShowInfo showInfo;
public ThreadA(ShowInfo showInfo) {
super();
this.showInfo = showInfo;
}
@Override
public void run() {
showInfo.showInfo();
}
}
class ThreadB extends Thread {
private ShowInfo showInfo;
public ThreadB(ShowInfo showInfo) {
super();
this.showInfo = showInfo;
}
@Override
public void run() {
showInfo.showInfo();
}
}
控制台输出结果:
线程名称为:A在1539525475324进入同步块
线程名称为:A在1539525478325离开同步块
线程名称为:B在1539525478325进入同步块
线程名称为:B在1539525481325离开同步块
这里线程争夺的是info的对象锁,两个线程有竞争同一对象锁的关系,出现同步
5、静态synchronized同步方法
public class SynRun {
public static void main(String[] args) {
ThreadAA a = new ThreadAA();
a.setName("A");
a.start();
ThreadBB b = new ThreadBB();
b.setName("B");
b.start();
}
}
class Service {
synchronized public static void printA() {
try {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
}
}
class ThreadAA extends Thread {
@Override
public void run() {
Service.printA();
}
}
class ThreadBB extends Thread {
@Override
public void run() {
Service.printB();
}
}
控制台输出结果:
线程名称为:B在1539525627704进入printB
线程名称为:B在1539525627704离开printB
线程名称为:A在1539525627704进入printA
线程名称为:A在1539525630705离开printA
两个线程在争夺同一个类锁,因此同步
6、运用synchronized (类.class)
public class SynRun {
public static void main(String[] args) {
ThreadAA a = new ThreadAA();
a.setName("A");
a.start();
ThreadBB b = new ThreadBB();
b.setName("B");
b.start();
}
}
class Service {
public static void printA() {
synchronized (Service.class) {
try {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
Thread.sleep(3000);
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printB() {
synchronized (Service.class) {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
}
}
}
class ThreadAA extends Thread {
@Override
public void run() {
Service.printA();
}
}
class ThreadBB extends Thread {
@Override
public void run() {
Service.printB();
}
}
控制台输出结果:
线程名称为:A在1539525736333进入printA
线程名称为:A在1539525739334离开printA
线程名称为:B在1539525739334进入printB
线程名称为:B在1539525739334离开printB
两个线程依旧在争夺同一个类锁,因此同步
需要特别说明:对于同一个类A,线程1争夺A对象实例的对象锁,线程2争夺类A的类锁,这两者不存在竞争关系。对象锁和类锁互互不干预
静态方法则一定会同步,非静态方法需在单例模式才生效,但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。另外,有必要说一下的是Spring的bean默认是单例的。
对象锁:锁的是类的对象实例。
类锁 :锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。
来自享学IT教育课后总结。
更多精彩敬请关注公众号
Java极客思维
微信扫一扫,关注公众号