注意点
- 锁的非原子性。同步方法(上锁了)并不表示该操作是原子操作,在其他线程中用仍然可以使用非同步锁或者是上了不同锁的方法该变
- SyncWithReentrantLock testOb = new SyncWithReentrantLock(); 最原始的锁,最灵活,也最难用(看普通锁)
- 对象锁,(java中每个对象都有一个锁)通常见到的锁,通过在方法定义时使用关键字Synchronized
- 其他对象锁。Synchronized(obj),获取对象obj的锁为己用,爽歪歪
起手式
构建一个线程的基本流程
public class Testthread {
private static final int DELAY = 10;
public static void main(String[] args) {
Runnable r1 = ()->{
try {
for(int i = 0;i<100;i++) {
System.out.println("我是小明");
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Runnable r2 = ()->{
try {
for(int i = 0;i<100;i++) {
System.out.println("我是小红");
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
}
中断一个线程
线程内的run方法执行完毕,并经由执行的return语句返回时,或者由于异常而中断时,线程退出。
并没有可以强制,但可以建议线程中断,线程检测到中断信号后可以对给信号做出反应
使用资源翻车
public class TestNoSync {
private static final int DELAY = 10;
public static void main(String[] args) {
final int[] ii = new int[2];
Arrays.fill(ii, 0);
Runnable r1 = ()->{
try {
for(int i = 0;i<1000;i++) {
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r1 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Runnable r2 = ()->{
try {
for(int i = 0;i<1000;i++) {
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r1 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
}
最后的输出结果应该是 2000,2000.可最后是
r1 i1 : 1799,i2 : 1821
r1 i1 : 1800,i2 : 1823
r1 i1 : 1800,i2 : 1823
r1 i1 : 1801,i2 : 1824
而且每次运行结果都不一样
上把普通锁
package thread;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncWithReentrantLock {
private static final int DELAY = 10;
final int[] ii = new int[2];
//锁变量
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
SyncWithReentrantLock testOb = new SyncWithReentrantLock();
testOb.test();
}
void test() {
Arrays.fill(ii, 0);
Runnable r1 = ()->{
//上锁
lock.lock();
try {
for(int i = 0;i<1000;i++) {
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r1 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}finally {
lock.unlock();
//释放锁
}
};
Runnable r2 = ()->{
lock.lock();
try {
for(int i = 0;i<1000;i++) {
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r1 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
}
}catch (InterruptedException e) {
// TODO: handle exception
}finally {
lock.unlock();
//释放锁
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
}
注:上锁不是将操作置为原子操作,而是阻止其他线程进入访问公共资源的临界区。上锁后的程序依旧可以被打断(时间到了,阻塞)。但上锁可以保证一点,在其他线程使用这把锁会陷入阻塞,从而不会访问公共资源,保证了代码的同步性。
在同一线程中,同一把锁可以多次使用,并不会引起阻塞。
条件对象
package thread;
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SyncWithReentrantLock {
private static final int DELAY = 10;
final int[] ii = new int[2];
//锁变量
private Lock lock = new ReentrantLock();
private Condition testCondition;
public static void main(String[] args) {
SyncWithReentrantLock testOb = new SyncWithReentrantLock();
testOb.test();
}
void test() {
testCondition = lock.newCondition();
//可以使用多个锁
Arrays.fill(ii, 0);
Runnable r1 = ()->{
//上锁
lock.lock();
try {
for(int i = 0;i<1000;i++) {
while(ii[0] % 100 == 99) {
System.out.println("wait");
testCondition.await();
}
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r1 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
testCondition.signalAll();
}
}catch (InterruptedException e) {
// TODO: handle exception
}finally {
lock.unlock();
//释放锁
}
};
Runnable r2 = ()->{
lock.lock();
try {
for(int i = 0;i<1000;i++) {
while(ii[0] % 100 == 50) {
System.out.println("wait");
testCondition.await();
}
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("r2 i1 : %s,i2 : %s %n" ,ii[0],ii[1]);
Thread.sleep(DELAY);
testCondition.signalAll();
}
}catch (InterruptedException e) {
// TODO: handle exception
}finally {
lock.unlock();
//释放锁
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
}
条件对象必须激活,负责这个线程将陷入永久阻塞。
当wait()时会放弃当前锁,所以要在使用公共资源之前检测条件对象
Synchronized,wait,notifyAll,notify简化的锁与条件对象
package thread;
import java.util.Arrays;
public class TestSynchronized {
private static final int DELAY = 10;
final int[] ii = new int[2];
public static void main(String[] args) {
TestSynchronized testOb = new TestSynchronized();
testOb.test();
}
void test() {
Arrays.fill(ii, 0);
Runnable r1 = ()->{
try {
incre(97);
Thread.sleep(DELAY);
}
catch (InterruptedException e) {
// TODO: handle exception
}
};
Runnable r2 = ()->{
try {
incre(50);
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
private synchronized void incre(int n) throws InterruptedException {
for(int i = 0;i<500;i++) {
while(ii[0] % 100 == n) {
System.out.println("wait");
wait();
}
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf("第 %s 次 : r1 i1 : %s,i2 : %s %n" ,i,ii[0],ii[1]);
notifyAll();
}
}
}
学习的时候尝试去掉notufyAll(),修改参数,加深对多线程的理解
这种方法只有一个系统默认的锁,但代码实现的很大的简化
synchronized利用的是对象锁,每个对象有且仅有一个,在线程中可以通过调用同步方法获取它,他的条件状态也只有一个
同步阻塞
同步阻塞是获得内部锁的另一种方式
注,这儿是获得另外一个对象的锁,如本例中获得obj的锁
package thread;
import java.util.Arrays;
public class TestSynchronized2 {
private static final int DELAY = 10;
final int[] ii = new int[2];
//无实用,为了使用每个java对象持有的锁
private Object obj = new Object();
public static void main(String[] args) {
TestSynchronized2 testOb = new TestSynchronized2();
testOb.test();
}
void test() {
Arrays.fill(ii, 0);
Runnable r1 = ()->{
try {
incre(97);
Thread.sleep(DELAY);
}
catch (InterruptedException e) {
// TODO: handle exception
}
};
Runnable r2 = ()->{
try {
incre(50);
}catch (InterruptedException e) {
// TODO: handle exception
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("start thread");
}
private void incre(int n) throws InterruptedException {
synchronized (obj) {
for(int i = 0;i<500;i++) {
while(ii[0] % 100 == n) {
System.out.println("wait");
obj.wait();
}
ii[0] = ii[0] + 1;
ii[1] = ii[1] + 1;
System.out.printf(Thread.currentThread() + "第 %s 次 : r1 i1 : %s,i2 : %s %n" ,i,ii[0],ii[1]);
Thread.sleep(DELAY);
obj.notify();
}
}
}
}
线程中其他重要细节
- volatile域(告诉编译器和虚拟机该域可能被并发)
- final变量
- 原子性(java.util.concurrent.atomic提供了很多高效的机器指令来保证操作的原子性)
- 死锁
- 线程局部变量(利用线程库为每个线程提供一个互不干扰的实例)
- 锁测试域超时
- 读写锁ReentrantLock。与普通锁ReentrantLock(前面提到过)只要的两把锁。读写锁允许读者线程共享访问,写线程互斥。
- 阻塞队列。生产者线程像队列中插入元素,消费者线程则取出他们
- Callabke 与 Future ,一套组合工具,与runable相比,多了返回值
- 执行器(executor)。创建线程池加强对线程的利用。有多种线程池可选
- 预定执行
- Fork-Join框架。将大任务分为几个小任务执行
- 可完成的Future。将各步工作组合起来
- 同步器。协调线程间合作。方法有信号量,倒计时门栓,障栅,交换器,同步队列