多线程之syncronized关键字使用方式总结
syncronized使用说明
1、前提
首先说明下:java中有三大变量:静态变量、成员变量、局部变量
静态变量和成员变量其实都是属于成员变量,所以分成两种类型。
那么一定不会出现问题的是局部变量,因为对于局部变量来说,都是处于线程的线程栈空间中,每个线程都有自己的线程栈,所以局部变量一定不会出现线程安全问题。
使用syncronized关键字的前提就是注意共享对象是哪些线程的共享对象,如果不是多个线程的共享对象,那么多线程之间将不会同步,而是会将导致多线程仍然是异步状态。
上面的线程对象也就是上面的三个变量中的一个,可以进行合理的使用。
下面通过几个案例来进行演示:
2、同步代码块
位置一
/**
* @Description 使用同步代码块来进行操作,注意共享变量是哪个
* @Author liguang
* @Date 2022/03/27/13:56
*/
public class SyncronizedTestString {
public static void main(String[] args) {
//创建五个线程来进行执行
for (int i = 0; i < 5; i++) {
new Thread(()->{
// 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
synchronized ("lig"){
try {
Thread.sleep(5000);
System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
System.out.println("当前主线程正在执行......................");
}
}
通过运行结果,可以看到多线程处于同步状态。多线程之间的共享对象是字符串对象。因为字符串对象是位于常量池中的,对于多线程来说,是可以进行共享的。
位置二
那么对这里来做一个延伸,将共享变量的位置进行提前到main线程中来:
public class SyncronizedTestStringMain {
public static void main(String[] args) {
String tmp = "lig";
//创建五个线程来进行执行
for (int i = 0; i < 5; i++) {
new Thread(()->{
// 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
synchronized (tmp){
try {
Thread.sleep(5000);
System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
System.out.println("当前主线程正在执行......................");
}
}
位置三
那么再次做一个提升:
public class SyncronizedTestStringClass {
private static String tmp = "lig";
public static void main(String[] args) {
//创建五个线程来进行执行
for (int i = 0; i < 5; i++) {
new Thread(()->{
// 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
synchronized (tmp){
try {
Thread.sleep(5000);
System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
System.out.println("当前主线程正在执行......................");
}
}
将共享对象提升到成员属性上来
3、同步实例方法
关于同步实例方法,对应的锁对象是当前类的对象,也就是this
demo1
public class Demo1 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
// 线程t1和线程t2使用的是同一个共享变量
MyThread t1 = new MyThread(myTask);
MyThread t2 = new MyThread(myTask);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
static final class MyThread extends Thread {
private MyTask myTask;
public MyThread(MyTask myTask){
this.myTask = myTask;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
// 调用show方法
myTask.show();
}
if (Thread.currentThread().getName().equals("t2")){
myTask.sleep();
}
}
}
static class MyTask {
// 加上了synchronized关键字
public synchronized void show() {
System.out.println("show method running.............");
try {
Thread.sleep(5000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有synchronized关键字
public void sleep() {
System.out.println("sleep method running.................");
}
}
}
分析:因为show方法加上了同步关键字,而sleep方法没有同步关键字,所以将会导致对象调用sleep方法不会有同步效果。
demo2
public class Demo2 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
// 线程t1和线程t2使用的是同一个共享变量
MyThread t1 = new MyThread(myTask);
MyThread t2 = new MyThread(myTask);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
static final class MyThread extends Thread {
private MyTask myTask;
public MyThread(MyTask myTask){
this.myTask = myTask;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
// 调用show方法
myTask.show();
}
if (Thread.currentThread().getName().equals("t2")){
myTask.sleep();
}
}
}
static class MyTask {
public synchronized void show() {
System.out.println("show method running.............");
try {
Thread.sleep(5000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sleep() {
System.out.println("sleep method running.................");
try {
Thread.sleep(2000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
show方法和sleep方法都加上了同步关键字,那么对应的效果就是会导致多线程会有同步效果
demo3
这里情况将不会有同步效果,因为这里的共享对象已经更换了,有了两个MyTask对象,但是线程不共享,所以不会有同步效果:
public class Demo3 {
public static void main(String[] args) {
// 两个线程共享对象都不一样,所以不能够来使用同一个对象来进行使用!
// 这里是不能够这么样来进行操作的
MyTask myTask1 = new MyTask();
MyTask myTask2 = new MyTask();
// 线程t1和线程t2使用的是同一个共享变量
MyThread t1 = new MyThread(myTask1);
MyThread t2 = new MyThread(myTask2);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
static final class MyThread extends Thread {
private MyTask myTask;
public MyThread(MyTask myTask){
this.myTask = myTask;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
// 调用show方法
myTask.show();
}
if (Thread.currentThread().getName().equals("t2")){
myTask.sleep();
}
}
}
static class MyTask {
public synchronized void show() {
System.out.println("show method running.............");
try {
Thread.sleep(5000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sleep() {
System.out.println("sleep method running.................");
try {
Thread.sleep(2000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4、同步静态方法
Demo4
那么拿上面的demo3来举例子,那么下面将方法上加上static关键字来进行更换,就又可以导致线程同步:
public class Demo4 {
public static void main(String[] args) {
// 两个线程共享对象都不一样,所以不能够来使用同一个对象来进行使用!
// 这里是不能够这么样来进行操作的
MyTask myTask1 = new MyTask();
MyTask myTask2 = new MyTask();
// 线程t1和线程t2使用的是同一个共享变量
MyThread t1 = new MyThread(myTask1);
MyThread t2 = new MyThread(myTask2);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
static final class MyThread extends Thread {
private MyTask myTask;
public MyThread(MyTask myTask){
this.myTask = myTask;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
// 调用show方法
myTask.show();
}
if (Thread.currentThread().getName().equals("t2")){
myTask.sleep();
}
}
}
static class MyTask {
public static synchronized void show() {
System.out.println("show method running.............");
try {
Thread.sleep(5000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void sleep() {
System.out.println("sleep method running.................");
try {
Thread.sleep(2000);
System.out.println("show method finished...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5、生产者消费者案例
public class ProducterConsumerTest {
public static void main(String[] args) {
List list = new ArrayList();
new Thread(new Producer(list)).start();
new Thread(new Consumer(list)).start();
}
static class Producer extends Thread{
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()>0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到这里,那么就一定是被激活了的
list.notify();
Object obj = new Object();
list.add(obj);
try {
System.out.println("Producer已经投递............"+obj);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Consumer extends Thread{
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
while (true){
synchronized (list){
if (list.size()==0){
try {
// 被notify的时候,肯定会来进行唤醒,然后代码继续会向下来进行运行!
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到这里,那么就一定是被激活了的
list.notify();
Object remove = list.remove(0);
System.out.println("消费者已经消费------>:"+remove);
try {
// 不会释放当前线程拥有锁的权利
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
6、总结:
怎么样才能够造成线程安全问题?三大条件:多线程、数据共享、多线程修改共享资源
共享数据指的是什么?成员变量:静态变量、成员变量,不可以进行修改的
怎么来进行解决?线程保证同步,常用的使用方式:syncronized和lock锁实现等等。
使用syncronzied关键字关键就在于共享对象是谁?如果和wait已经notify(nofitfyAll)方法结合使用的时候,要使用共享对象来进行通知,才能够达到对应的效果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?