前言
一个进程内可以存在多个线程,一个线程也可以存在多个协程。
在一般的开发环境中,会经常利用到线程。
线程的创建方式
extends Thread
直接继承Thread类,重写run()方法implements Runnable
最常用的创建方式,实现Runnable接口,重写run()方法implements Callable<T>
有返回值,重写run()方法
推荐使用implements的方法创建线程
synchronized
使用场景
- 直接在创建的线程类中加关键字synchronized
- 新建一个公有类,可作为一项服务,多个线程对该服务进行访问。在这个公有类中加关键字synchronized
在何处加
我们在这里定义一个场景:一个线程可以不停的抢票,直到票数为0
1. 不加synchronized
class MyThread implements Runnable{
//定义100张票
private int tickets = 100;
//打印抢票信息
public void print(){
System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
}
@Override
public /*synchronized*/ void run() {
//这里的run方法未添加synchronized
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1,"A").start();
new Thread(myThread1,"B").start();
new Thread(myThread1,"C").start();
new Thread(myThread1,"D").start();
new Thread(myThread1,"E").start();
}
}
部分运行结果
不使用synchronized同步,会出现数据的脏读
2. 修饰方法
class MyThread implements Runnable{
private int tickets = 100;
public void print(){
System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
}
@Override
public synchronized void run() {
//用synchronized修饰
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1,"A").start();
new Thread(myThread1,"B").start();
new Thread(myThread1,"C").start();
new Thread(myThread1,"D").start();
new Thread(myThread1,"E").start();
}
}
部分运行结果
使用synchronized同步,数据按照我们所想的方向运行
3. 修饰this
在此部分,只改动run方法的代码
@Override
public void run() {
synchronized (this){
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
输出结果和2部分相同,仍是同步的
this 表示的是当前对象的引用,当前对象是谁?线程1、2都实现了同一个方法,自然他们的对象是相同的,this指向的对象都是同一个对象,自然会线程安全、同步输出。
那如果我加一行代码呢?
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"是非synchronized修饰的方法内的输出");
synchronized (this){
while(this.tickets > 0){
this.print();
this.tickets--;
}
}
}
看一下输出结果:
在《Java多线程编程核心技术》一书中,有这样一段话: “不在synchronized块中就是异步执行,在synchronized块中就会死同步执行”
上面的例子中,run()方法分为两部分,一部分是普通的输出print,另一部分是synchronized修饰的部分。
普通的输出print部分:异步执行。
synchronized修饰部分:同步执行
【抽象说】
线程A:我们start()了!!!冲!冲!冲!!!!!!!!!!
(众人):冲!!!!run()!run()!run()!
(众人):我输出了!我输出了!我输出了!我输出了!我输出了!...
(由于线程B身强体壮,起跑快,冲到了第一个)
线程B:耽误太多时间,事情可就做不完了。抱歉了,兄弟们,先走一步!(说完便拿起锁)
(此时的synchronized块,被线程B私有)
(众人):卧槽!别啊!给我们锁啊!别让我们阻塞!
线程B:哇哦!AWESOME MAN!!鹅鹅鹅!这是我独享的MOMENT!!!!!
(线程B抢完了所有的票)
4. 方法内修饰变量
synchronized() 修饰的应是Object类
此时我们换一个稍微复杂点的场景
有两个人前来买瓜,有个人卖瓜 ; 瓜摊上有两个瓜:一个10斤、一个15斤 ; 单价2块钱
买瓜的:我 和 华强
我买10斤的,华强买15斤的
我花20块钱,华强花30块钱
- 变量定义在类中 (类中的全局变量)
class Melon{
private final int singlePrice = 2; //单价
private int weight; //重量
String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";
public void setWeight(int weight) {
try{
synchronized (words){
System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
this.weight = weight ;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//我的线程
class Me extends Thread{
Melon melon;
Me(Melon melon){
super();
this.melon = melon;
}
@Override
public void run() {
melon.setWeight(10);
}
}
//华强的线程
class HuaQiang extends Thread{
Melon melon;
HuaQiang(Melon melon){
super();
this.melon = melon;
}
@Override
public void run() {
melon.setWeight(15);
}
}
public class RunnableDemo {
public static void main(String[] args) {
Melon melon = new Melon();
Thread me = new Me(melon);
Thread huaqiang = new HuaQiang(melon);
me.setName("我");
huaqiang.setName("华强");
me.start();
huaqiang.start();
}
}
输出结果
可以看到时同步输出,排队
- 变量定义在方法中(方法内的局部变量)
将words变量放在方法内定义
public void setWeight(int weight) {
String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";
try{
synchronized (words){
System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
this.weight = weight ;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
可以看到,此时两个线程同时到达函数体内部,是异步运行,而且注意到线程并不安全
【抽象说】
我和华强同时到了,准备了不同的话(String中的内容不同)
我抢先一句:老板,来个瓜
华强几乎同时说:给我挑一个
老板懵了,看到我们是两句话,使用了影分身。
老板1号给我的瓜称重,10斤,20块。
老板2号给华强的瓜称重,15斤,30块。(稍慢了0.00000001纳秒
⚠️此时秤砣是一个,30块覆盖了20块。
老板1号对我说:30块。
老板2号对华强说:30块。
我:???
5. synchronized修饰静态
无论是静态方法还是静态变量,在类的初始化中都会率先执行;同时也侧面说明了,静态方法/静态变量是和类绑定的。
同时也可以推断出,synchronized修饰静态,也同时将class类修饰。
class类是单例的,当用synchronized修饰静态时,针对相同类、不同对象、不同方法,仍会同步运行。