概述
线程本身由于创建和切换的开销,采用多线程不会提高程序的执行速度,反而会降低速度,但是对于频繁IO操作的程序,多线程可以有效的并发。
对于包含不同任务的程序,可以考虑每个任务使用一个线程。这样的程序在设计上相对于单线程做所有事的程序来说,更为清晰明了,如果是单纯的计算操作,多线程并没有单线程的计算效率高,但是对于一些刻意分散使用计算机系统资源的操作,则适合使用多线程。
在实际的开发中对于性能优化的问题需要考虑到具体的场景来考虑是否使用多线程技术。一般来说一个程序是运行在一个进程中的,进程是具有一定独立功能的程序、它是计算机系统进行资源分配和调度的一个独立单位。而线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位。
在JMM中,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另一个线程还在继续使用它在寄存器中的变量值的拷贝,造成数据的不一致,这样就会导致线程不安全,下面介绍几种Java中常见的线程同步的方式。
正文
关于线程不安全的原因是因为JMM定义了主内存跟工作内存,造成多个线程同事访问同一个资源时导致的不一致问题,那么要想解决这个问题其实也很简单,也是从JMM入手,主要有以下3种方式,
- 保证每个线程访问资源的时候获取到的都是资源的最新值(可见性)
- 当有线程 操作该资源的时候锁定该资源,禁止别的线程访问(锁)
- 线程本地私有化一份本地变量,线程每次读写自己的变量(ThreadLocal)
锁
synchronized
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个锁标记,当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池,互斥锁分两种一种是类锁,一种是对象锁。
类锁:用于类的静态方法或者一个类的class,一个对象只有一个
对象锁:用于实例化的对象的普通方法,可以有多个
下面还是用程序员改bug这个例子来示范一下synchronized的使用方式
Bug类
public class Bug {
private static Integer bugNumber = 0;
public static int getBugNumber() {
return bugNumber;
}
//普通同步方法
public synchronized void addNormal() {
bugNumber++;
System.out.println("normalSynchronized--->" + getBugNumber());
}
//静态同步方法
public static synchronized void addStatic() {
bugNumber++;
System.out.println("staticSynchronized--->" + getBugNumber());
}
//同步代码块
public synchronized void addBlock() {
synchronized (bugNumber) {
this.bugNumber = ++bugNumber;
System.out.println("blockSynchronized--->" + getBugNumber());
}
}
}
Runnable
public class BugRunnable implements Runnable {
private Bug mBug=new Bug();
@Override
public void run() {
mBug.addNormal();//普通方法同步
// mBug.addBlock();//同步代码块
// Bug.addStatic();//静态方法同步
}
}
测试代码
public static void main(String[] args) {
BugRunnable bugRunnable = new BugRunnable();
for (int i = 0; i < 6; i++) {
new Thread(bugRunnable).start();
}
}
同步代码块
//同步代码块
public synchronized void addBlock() {
synchronized (bugNumber) {
this.bugNumber = ++bugNumber;
System.out.println("blockSynchronized--->" + getBugNumber());
}
}
测试结果
blockSynchronized--->1
blockSynchronized--->2
blockSynchronized--->3
blockSynchronized--->4
blockSynchronized--->5
blockSynchronized--->6
普通方法同步
//普通同步方法
public synchronized void addNormal() {
bugNumber++;
System.out.println("normalSynchronized--->" + getBugNumber());
}
测试结果
normalSynchronized--->1
normalSynchronized--->2
normalSynchronized--->3
normalSynchronized--->4
normalSynchronized--->5
normalSynchronized--->6
静态方法同步
//静态同步方法
public static synchronized void addStatic() {
bugNumber++;
System.out.println("staticSynchronized--->" + getBugNumber());
}
测试结果
staticSynchronized--->1
staticSynchronized--->2
staticSynchronized--->3
staticSynchronized--->4
staticSynchronized--->5
staticSynchronized--->6
对比分析
- 类的每个实例都有自己的对象锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问同一个实例(因为对象可以有多个实例)同步代码块或同步方法,必须等待当前线程释放掉对象锁才可以,如果是访问类的另外一个实例,则不需要。
- 如果一个对象有多个同步方法或者代码块,没有获取到对象锁的线程将会被阻塞在所有同步方法之外,但是可以访问非同步方法
- 对于静态方法,实际上可以把它转化成同步代码块,就拿上面的静态方法,实际上相当于:
//静态同步方法
public static synchronized void addStatic() {
bugNumber++;
System.out.