多线程并发的好处、问题以及synchronized关键字的使用方法
一、线程简介
什么线程呢?简单来说线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。
二、多线程的好处
那我们为什么要使用多线程呢?
这里有个例子能很好地说明
Scanner s = new Scanner(System.in);
for (int i = 0; i < 100; i++) {
System.out.println("A工作了"+i);
if(i==50){
System.out.println("A工作了一般,是否继续?");
String ans = s.next();
if(ans == "y"){
continue;
}
else{
System.out.println("A下班了");
break;
}
}
}
for (int i = 0; i < 100; i++) {
System.out.println("B工作了"+i);
}
上述代码中,首先执行A,在A执行了一半时询问是否继续执行。在这段时间B一直处于等待状态,如果A一直不回应那么B永远也无法工作。
在实际情况中也有很多类似的例子,如果你在使用电脑连接微信或者QQ网速比较慢,在单线程的情况下,你只能盯着服务器的连接什么也做不了,就像上述代码中的小B一样。
如果我们使用两个线程将A B分开工作
public class test07 {
public static void main(String[] args) {
ATask aTask = new ATask();
BTask bTask = new BTask();
aTask.start();
bTask.start();
}
}
class ATask extends Thread{
@Override
public void run() {
Scanner s = new Scanner(System.in);
for (int i = 0; i < 100; i++) {
System.out.println("A工作了" + i);
if (i == 50) {
System.out.println("A工作了一般,是否继续?");
int ans = s.nextInt();
if (ans == 111) {
continue;
} else {
System.out.println("下班啦");
break;
}
}
}
}
}
class BTask extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("B工作了" + i);
}
}
}
这样AB的工作就互不相关了,无论A那里停多久B都继续执行他的任务
总的来说,多线程的优点有以下几个
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)占用大量处理时间的任务使用多线程可以提高CPU利用率,即占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(3)多线程可以分别设置优先级以优化性能。
三、多线程的问题
多线程有许多优点相应的也会带来一些问题,通常多线程在工作时会访问各种各样的数据,当两个线程同时获取了同一个成员变量进行处理时,该数据就可能出现问题。例如仓库中有三箱货物,线程一和线程二同时获取了三箱货物分别用了一箱,理论上还有一箱货物,实际结果我们可以测试一下
public class test07 {
public static int productNum= 3;
public static void main(String[] args) {
System.out.println("剩余产品还有"+productNum+"箱");
ATask aTask = new ATask();
BTask bTask = new BTask();
aTask.start();
bTask.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余产品还有"+productNum+"箱");
}
}
class ATask extends Thread{
@Override
public void run() {
int num = test07.productNum;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("任务A消耗了一箱产品");
test07.productNum=num;
}
}
class BTask extends Thread{
@Override
public void run() {
int num = test07.productNum;
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("任务B消耗了一箱产品");
test07.productNum=num;
}
}
我们发现上述代码的结果和理论结果不同,是因为任务A和任务B在消耗产品之前拿到的产品数量都为3,所以两个线程消耗完产品后都认为剩余两箱才有以下结果。
也就是说产品数量同时被两个线程拿到是不安全的
那我们是否有一种方法可以每次只让一个线程拿到数据呢?
四、synchronized关键字
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
被synchronized关键词修饰的对象简单来说会加上一把锁,当一个线程获取该对象时,就将该变量上锁。后续线程就不能获取该变量,直到上一个线程使用完该变量释放掉手中的锁
public class test07 {
public static Integer productNum= 3;
public static void main(String[] args) {
System.out.println("剩余产品还有"+productNum+"箱");
ATask aTask = new ATask();
BTask bTask = new BTask();
aTask.start();
bTask.start();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余产品还有"+productNum+"箱");
}
}
class ATask extends Thread{
@Override
public void run() {
synchronized(test07.productNum){
int num = test07.productNum;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("任务A消耗了一箱产品");
test07.productNum=num;
}
}
}
class BTask extends Thread{
@Override
public void run() {
synchronized(test07.productNum){
int num = test07.productNum;
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("任务B消耗了一箱产品");
test07.productNum=num;
}
}
}
加入synchronized关键词后,结果如下
以上就是对于多线程并发总结的部分优点、问题和解决方法。