1 多线程自增问题
1.1一个线程自增问题
前面我们学习了多线程,那么我们就可以创建线程来帮我们干许多事,下面我们写一个简单的线程来实现最简单的加法运算吧。
写一个线程将addNum加1000次:
用面向对象的思维来写,首先要写一个声明addNum数和其加法得类,其次写一个工作类,这个类要继承Thread或者实现Runable,最后写一个测试类。
Plus声明addNum类
package ThreadAdd;
public class Plus {
private int addNum=0;
public int getAddNum() {
return addNum;
}
public void setAddnum(int addNum) {
this.addNum = addNum;
}
public void add(){
addNum++;
}
}
MyThread工作类
package ThreadAdd;
public class MyThread implements Runnable{
private Plus plus;
public MyThread() {
}
public MyThread(Plus plus) {
this.plus = plus;
}
@Override
public void run() {
for(int i = 0;i < 1000; i ++){
plus.add();
}
}
}
Test测试类
package ThreadAdd;
public class Test {
public static void main(String[] args) {
Plus plus = new Plus();
MyThread myThread = new MyThread(plus);
Thread thread = new Thread(myThread);
thread.start();
try {
thread.join(); //join让子线程插入到main线程来,等子线程结束后在执行main线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("addNum的值="+plus.getAddNum());
}
}
执行结果为
1.2 多个线程自增
一个线程自增问题解决了,那么现在我们再多加三个子线程,都给addNum加10000,那我们就能得到40000了,那么答案是这样吗?实验出真知,说干就干。
package ThreadAdd;
public class Test {
public static void main(String[] args) throws InterruptedException {
Plus plus = new Plus();
MyThread myThread = new MyThread(plus);
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
System.out.println("addNum的值="+plus.getAddNum());
}
}
按照代码来说,现在有4条线程来实现加法,将这4条都插入到main线程里面,等这4条线程都执行完执行main方法,输出addNum的值。
结果
结果为11744,而且如果你重新执行的话,这个数会不断变化,但是这个数就是比40000要小得多,这是为什么呢?
1.3 多线程自增问题解答
得到的数比40000要小的多,原因是:多线程是抢占式调度模型,这4个线程不是同时执行的,这4个线程分别抢CPU的执行权限,CPU会分配其时间片来执行程序,当线程1抢到CPU的执行权限时,其他三个线程等待。有可能出现一种状况:这4个线程都访问到addNum的值为500,但是现在线程1抢到CPU的执行权限,那么现在这个线程1将addNum的值加到800了,这时他执行结束,线程3抢到了CPU的执行权限,但是因为他访问的addNum的值为500,所以线程3就从500开始加,加到1000。那么线程1从500加到800和线程3从500加到1000,这其中重复加了许多次,这样就造成资源的浪费,且这个数永远要比40000小。
那么,如何解决呢?
这时就需要加一把锁,比如当线程1抢到CPU的执行权限时,它先访问临界代码块(多个线程共同执行的任务),将临界代码块加一把锁,这样这有线程1可以执行那段代码(这时线程1已将addNum加到一定值然后更新addNum值),等线程1时间片使用结束时,锁释放,其他抢到CPU执行权限的线程才可以访问临界代码块,像线程1一样加锁,执行完之后释放锁…。
具体实现
package ThreadAdd;
public class MyThread implements Runnable{
private Plus plus;
public MyThread() {
}
public MyThread(Plus plus) {
this.plus = plus;
}
@Override
public void run() {
synchronized (plus){
for (int i = 0; i < 10000; i++) {
plus.add();
}
}
}
}
把锁加到临界代码块就可以了
2 内置锁
在上面我们用了锁,那这个锁究竟是什么,具体怎么使用呢?
2.1 简介
synchronized顾名思义,就是用来进行一些同步工作的,我们常常在多线程的环境中使用到它,实现互斥的效果。
每一个java对象都可以当做一个同步锁,线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得锁的唯一途径就是进入这个锁的保护的同步代码块或方法。这里的同步代码块和同步方法,就是使用synchronized关键字标记的代码块和方法。
synchronized内置锁
解释两个概念:
- 临界区资源(共同数据)
表示一种可以被多个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。一旦临界区资源被占用,想使用该资源的其他线程必须等待。 - 临界区代码段(Critical Section)
是每个线程中访问临界资源的那段代码,多个线程必须互斥地对临界区资源进行访问。线程进入临界区代码段之前,必须在进入区申请资源,申请成功之后进行临界区代码段,执行完成之后释放资源。临界区代码段(Critical Section)的进入和退出
2.2 语法
synchronized (共享对象(plus,ticket)) {//临界区代码块
//对共对象的访问(plus)(ticket)
}
2.3 实例
实例方法上加锁
public class SynDemo1 {
public static void main(String[] args) {
Car a = new Car();
Thread t1 = new Thread1(a);
Thread t2 = new Thread2(a);
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
private Car a;
public Thread1(Car a) {
this.a = a;
}
public void run() {
a.fun1();
}
}
class Thread2 extends Thread {
private Car a;
public Thread2(Car a) {
this.a = a;
}
public void run() {
a.fun2();
}
}
class Car {
public synchronized void fun1() {
synchronized (this) {
System.out.println("开始打蜡");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打蜡结束");
}
}
public void fun2() {
synchronized (this) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始抛光");
System.out.println("抛光结束");
}
}
}
静态方法加锁
public class SynDemo1 {
public static void main(String[] args) {
Car a = new Car();
Thread t1 = new Thread1(a);
Thread t2 = new Thread2(a);
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
private Car a;
public Thread1(Car a) {
this.a = a;
}
public void run() {
a.fun1();
}
}
class Thread2 extends Thread {
private Car a;
public Thread2(Car a) {
this.a = a;
}
public void run() {
a.fun2();
}
}
class Car {
public synchronized static void fun1() { //给方法上锁
System.out.println("开始打蜡");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打蜡结束");
}
public static void fun2() {
synchronized (Car.class) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始抛光");
System.out.println("抛光结束");
}
}
}