1.并发、并⾏、进程、线程
并发与并⾏
- 并发:指两个或多个事件在同⼀个时间段内发⽣。
- 并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣)。
在操作系统中同时运行多个程序,在单核CPU系统中看似是同一时刻在运行不同的程序,实际是每一时刻只有一个程序在运行。在系统运行时,会在CPU上为不同的程序分配不同的时间片,运行的程序获取到时间片时才是真正的运行,不同时间片切换称之为上下文切换,上下文切换的时间很短十几毫秒甚至几毫秒就完成,所以给人视觉感觉是同时运行。
在多核CPU系统中,这些同时运行的程序会分配到不同的处理器上执行,实现多任务并行执行。这样可以大大的提高运行效率。
线程与进程
- 进程:是指⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是⼀个进程从创建、运⾏到消亡的过程。
- 线程:线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序。
线程调度
- 分时调度:所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间。
- 抢占式调度:优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性),Java使⽤的为抢占式调度。
2.创建线程
(1)继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println(Thread.currentThread().getName()+": main方法执行!");
}
执行结果为:
把start方法换成run方法再执行一次
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.run();
System.out.println(Thread.currentThread().getName()+": main方法执行!");
}
结论:在执行线程的时候不可以使用run()方法执行线程,要使用start()方法。使用run方法,会使用main线程按照代码先后顺序执行,MyThread代码执行不受影响,但是失去了新建线程的意义。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
在Thread源码中start()方法调用了start0(),start0()是个native方法调用的jvm,其实在JVM内部调用的是JavaThread方法,JavaThread又调用了操作系统PThread_create创建线程。
(2)实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.setName("新建runnable线程");
t1.start();
System.out.println(Thread.currentThread().getName()+": main方法执行!");
}
继承Thread和实现Runnable的区别:
1.继承Runnable,不适合资源共享。实现Runnable很容易实现资源共享。
2.实现Runnable可以避免java的单继承的局限性。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程是独立的。
public class MyThread extends Thread{
private int stick = 20;
@Override
public void run() {
while(stick>0){
System.out.println(Thread.currentThread().getName()+": "+stick --);
}
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.setName("新建线程1");
myThread1.start();
MyThread myThread2 = new MyThread();
myThread2.setName("新建线程2");
myThread2.start();
MyThread myThread3 = new MyThread();
myThread3.setName("新建线程3");
myThread3.start();
}
新建线程1: 20
新建线程1: 19
新建线程1: 18
新建线程1: 17
新建线程1: 16
新建线程1: 15
新建线程1: 14
新建线程1: 13
新建线程1: 12
新建线程1: 11
新建线程1: 10
新建线程1: 9
新建线程1: 8
新建线程1: 7
新建线程1: 6
新建线程1: 5
新建线程1: 4
新建线程1: 3
新建线程1: 2
新建线程1: 1
新建线程2: 20
新建线程2: 19
新建线程2: 18
新建线程2: 17
新建线程2: 16
新建线程2: 15
新建线程2: 14
新建线程2: 13
新建线程3: 20
新建线程3: 19
新建线程3: 18
新建线程3: 17
新建线程3: 16
新建线程3: 15
新建线程2: 12
新建线程3: 14
新建线程3: 13
新建线程3: 12
新建线程3: 11
新建线程3: 10
新建线程3: 9
新建线程3: 8
新建线程3: 7
新建线程3: 6
新建线程3: 5
新建线程3: 4
新建线程3: 3
新建线程3: 2
新建线程3: 1
新建线程2: 11
新建线程2: 10
新建线程2: 9
新建线程2: 8
新建线程2: 7
新建线程2: 6
新建线程2: 5
新建线程2: 4
新建线程2: 3
新建线程2: 2
新建线程2: 1
从以上运行结果可以看出3个线程都是从20开始做--运算,每个线程都执行了20次。
public class MyRunnable implements Runnable{
private int stick = 20;
@Override
public void run() {
while(stick>0){
System.out.println(Thread.currentThread().getName()+": "+stick --);
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.setName("新建runnable线程1");
t1.start();
Thread t2 = new Thread(myRunnable);
t2.setName("新建runnable线程2");
t2.start();
Thread t3 = new Thread(myRunnable);
t3.setName("新建runnable线程3");
t3.start();
}
新建runnable线程1: 20
新建runnable线程2: 19
新建runnable线程3: 17
新建runnable线程1: 18
新建runnable线程1: 14
新建runnable线程1: 13
新建runnable线程1: 12
新建runnable线程3: 15
新建runnable线程2: 16
新建runnable线程2: 9
新建runnable线程2: 8
新建runnable线程2: 7
新建runnable线程2: 6
新建runnable线程2: 5
新建runnable线程2: 4
新建runnable线程2: 3
新建runnable线程2: 2
新建runnable线程2: 1
新建runnable线程3: 10
新建runnable线程1: 11
从以上运行结果可以看出3个线程做--运算,基数是一个数,不是3个。
结论:实现Runnable更适合资源共享。
其实继承Thread也可以实现资源共享,要把stick变量定义成static。
public class MyThread extends Thread{
private static int stick = 20;
@Override
public void run() {
while(stick>0){
System.out.println(Thread.currentThread().getName()+": "+stick --);
}
}
}
新建线程1: 20
新建线程2: 19
新建线程2: 17
新建线程2: 16
新建线程2: 15
新建线程2: 13
新建线程2: 12
新建线程2: 11
新建线程2: 10
新建线程2: 9
新建线程2: 8
新建线程2: 7
新建线程1: 18
新建线程1: 5
新建线程1: 4
新建线程1: 3
新建线程1: 2
新建线程1: 1
新建线程2: 6
新建线程3: 14
(3)实现Callable方法,结合线程池使用。
class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());
final Integer integer = future.get();
System.out.println(integer);
}
}
(4)使用lambda
new Thread(() ‐ > System.out.println(Thread.currentThread().getName())).start();
3.线程生命周期
(1)JAVA线程状态
public enum State {
/**
* Thread state for a thread which has not yet started.
* 新建状态:new出了一个新线程但是还没有开始的状态
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
* 可执行状态(就绪状态):当调⽤线程对象的start()⽅法( t.start(); ),线程即进⼊就绪状态。
* 处于就绪状态的线程,只是说 明此线程已经做好了准备,随时等待CPU调度执⾏,
* 并不是说执⾏了t.start()此线程⽴即就会执⾏;
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
* 阻塞状态:
* 等待监视器锁而阻塞的线程的线程状态。处于阻塞状态的线程正在等待监视锁进入同步块/方法,
* 或者在调用Object.wait后重新进入同步块/方法。
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
* 等待状态:
* 调用了wait() ,无参join(),LockSupport.park会进入等待状态
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
* 定时等待状态:调用了sleep,wait(long),join(long),LockSupport.parkNanos,LockSupport.parkUntil会进入定时等待状态
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
* 终止状态:线程已完成运行
*/
TERMINATED;
}
- NEW:新建状态,new出了一个新线程但是还没有开始的状态。
- RUNNABLE:可执行状态(就绪状态),当调⽤线程对象的start()⽅法( t.start(); ),线程即进⼊就绪状态。处于就绪状态的线程,只是说 明此线程已经做好了准备,随时等待CPU调度执⾏,并不是说执⾏了t.start()此线程⽴即就会执⾏。
- BLOCKED:阻塞状态,等待监视器锁而阻塞的线程的线程状态。处于阻塞状态的线程正在等待监视锁进入同步块/方法,或者在调用Object.wait后重新进入同步块/方法。
- WAITING:等待状态,调用了wait() ,无参join(),LockSupport.park会进入等待状态。
- TIMED_WAITING:定时等待状态,调用了sleep,wait(long),join(long),LockSupport.parkNanos,LockSupport.parkUntil会进入定时等待状态。
- TERMINATED:终止状态,线程已完成运行。
(2)操作系统线程状态
- 初始状态,指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
- 可运行状态,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
- 当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到CPU 的线程的状态就转换成了运行状态。
- 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
- 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
4.线程常用方法
方法名 | 说明 |
---|---|
public static void sleep(long millis)
|
当前线程主动休眠 millis 毫秒。
|
public static void yield()
|
当前线程主动放弃时间⽚,回到就绪状态,竞争 下⼀次时间⽚。
|
public final void join()
|
允许其他线程加⼊到当前线程中。
|
public void setPriority(int)
|
线程优先级为1-10,默认为5,优先级越⾼,表示 获取CPU机会越多。
|
public void setDaemon(boolean)
|
设置为守护线程线程有两类:⽤户线程(前台线程)、守护线程(后台线程)
|
public final void stop() | 强制终止线程 |
(1)线程休眠
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 3; i > 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("倒计时:"+i);
}
}
}
(2)线程让步
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
class Runnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(new Runnable1()).start();
new Thread(new Runnable2()).start();
}
}
没有让步的结果
Thread-1: 0
Thread-1: 1
Thread-1: 2
Thread-1: 3
Thread-1: 4
Thread-1: 5
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-1: 6
Thread-1: 7
Thread-1: 8
Thread-1: 9
Thread-0: 9
Thread-0: 10
Thread-0: 11
Thread-0: 12
Thread-0: 13
Thread-0: 14
Thread-0: 15
Thread-0: 16
Thread-0: 17
Thread-0: 18
Thread-0: 19
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("A: "+i);
}
}
}
class Runnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 线程让步
Thread.yield();
System.out.println("B: "+i);
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(new Runnable1()).start();
new Thread(new Runnable2()).start();
}
}
让步后的结果
A: 0
A: 1
A: 2
B: 0
A: 3
A: 4
A: 5
B: 1
B: 2
B: 3
B: 4
B: 5
A: 6
B: 6
B: 7
B: 8
A: 7
B: 9
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19
让步后的结果也没有出现线程A的结果都在B的前面
结论:
- Thread.yield() ⽅法作⽤是:暂停当前正在执⾏的线程对象(及放弃当前拥有的cup资源),并执⾏其他线程。
- yield() 做的是让当前运⾏线程回到可运⾏状态,以允许具有相同优先级的其他线程获得运⾏机会。因此,使⽤ yield() 的⽬的是让相同优先级的线程之间能适当的轮转执⾏。但是,实际中⽆法保证 yield() 达到让步⽬的,因为让步的线程还有可能被线程调度程序再次选中。
sleep()和yield()的区别
- sleep()使当前线程进⼊停滞状态(不可运行状态),所以执⾏sleep()的线程在指定的时间内肯定不会被执⾏;
- yield()只是使当前线程重新回到可执⾏状态,所以执⾏yield()的线程有可能在进⼊到可执⾏状态后⻢上⼜被执⾏。
(3)线程的合并
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("A: "+i);
}
}
}
public class Test {
public static void main(String[] args) {
System.out.println("主线程运行开始");
new Thread(new Runnable1()).start();
System.out.println("主线程运行结束");
}
}
子线程没有合并到主线程结果为:
主线程运行开始
主线程运行结束
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("A: "+i);
}
}
}
public class Test {
public static void main(String[] args) {
System.out.println("主线程运行开始");
try {
Thread thread = new Thread(new Runnable1());
thread.start();
// 合并到主线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行结束");
}
}
主线程运行开始
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19
主线程运行结束
(5)设置优先级:Java线程的优先级⽤整数表示,取值范围是1~10,有以下三个静态常量
- static int MAX_PRIORITY :线程可以具有的最⾼优先级,取值为10。
- static int MIN_PRIORITY: 线程可以具有的最低优先级,取值为1。
- static int NORM_PRIORITY: 分配给线程的默认优先级,取值为5。
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable1());
thread1.setName("线程A");
Thread thread2 = new Thread(new Runnable1());
thread2.setName("线程B");
Thread thread3 = new Thread(new Runnable1());
thread3.setName("线程C");
thread1.setPriority(Thread.MAX_PRIORITY);
thread3.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
线程B: 0
线程B: 1
线程B: 2
线程B: 3
线程B: 4
线程C: 0
线程A: 0
线程A: 1
线程A: 2
线程A: 3
线程A: 4
线程C: 1
线程C: 2
线程C: 3
线程C: 4
设置线程A的优先级为10,B为默认的5,C为1,执行结果并没有按照A-B-C的顺序来。
调整线程优先级:Java线程有优先级,优先级⾼的线程会获得较多的运⾏机会。优先级 : 只能反映 线程的紧急程度 , 不能决定是否⼀定先执⾏。
(6)守护线程:如果程序中所有前台线程都执⾏完毕了,后台线程会⾃动结束。setDaemon(true)设置守护线程。
线程有两类:⽤户线程(前台线程)、守护线程(后台线程)
class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable1());
thread1.setName("守护线程A");
// 设置thread1为守护线程
thread1.setDaemon(true);
thread1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程:"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主线程:0
主线程:1
主线程:2
守护线程A: 0
主线程:3
主线程:4
守护线程A: 1
主线程:5
主线程:6
主线程:7
守护线程A: 2
主线程:8
主线程:9
守护线程A: 3
结果:当主线程结束时,守护线程A虽然没有执行完,但是也结束了。
(7)stop()
public class ThreadStopDemo {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("A:等待获取锁");
synchronized (lock) {
System.out.println("A:获取锁开始执行");
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A:执行完成");
});
thread.start();
Thread.sleep(2000);
// 停止thread,并释放锁
thread.stop();
new Thread(() -> {
System.out.println("B:等待获取锁");
synchronized (lock) {
System.out.println("B:获取锁开始执行");
}
System.out.println("B:执行完成");
}).start();
}
}
A:等待获取锁
A:获取锁开始执行
B:等待获取锁
B:获取锁开始执行
B:执行完成
如果通过stop执行线程终止,A线程还没有执行完,强制终止,这样会造成数据不一致,业务逻辑不完整,Java提供了另一种线程中断机制。
Java线程的中断机制:Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被 中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选 择压根不停止。
- interrupt():将线程的中断标志位设置为true,不会停止线程
- isInterrupted():判断当前线程的中断标志位是否为true,不会清除中断标志位
- Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle
public class ThreadStopDemo {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!Thread.currentThread().isInterrupted() && i<1000){
i++;
System.out.println("轮训任务:"+i);
}
System.out.println("线程结束!");
});
thread.start();
// 主线程休眠1毫秒后增中断加标记位
Thread.sleep(1);
// 中断标记
//thread.interrupt();
}
}
不加中断标记线程正常执行,进行1000次轮训
加上中断标记后线程运行到126(随机的数,看cup执行效率),检测到标记位为true,程序停止运行,这个标记位是被中断线程自己可控的。
public class ThreadStopDemo {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
Thread.interrupted();
while(!Thread.currentThread().isInterrupted() && i<1000){
i++;
System.out.println("轮训任务:"+i);
}
System.out.println("线程结束!");
});
thread.start();
// 主线程休眠500毫秒后增中断加标记位
Thread.sleep(1);
thread.interrupt();
}
}
使用 Thread.interrupted()进行判断会清除标记位
sleep()方法也会清除标记位
public class ThreadStopDemo {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted() && i < 10) {
System.out.println("轮训任务:" + i++);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
// Thread.currentThread().interrupt();
}
}
System.out.println("线程结束!");
});
thread.start();
// 主线程休眠500毫秒后增中断加标记位
Thread.sleep(500);
thread.interrupt();
}
}
针对sleep的情况,要在cache异常中重新设置标记位
public class ThreadStopDemo {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted() && i < 10) {
System.out.println("轮训任务:" + i++);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
System.out.println("线程结束!");
});
thread.start();
// 主线程休眠500毫秒后增中断加标记位
Thread.sleep(500);
thread.interrupt();
}
}
sleep和wait方法都会导致标记位失效,sleep报异常sleep interrupted,wait报异常InterruptedException。
5.线程安全问题
为什么会出现线程安全问题?
- 当多线程并发访问临界资源时,如果破坏原⼦操作,可能会造成数据不⼀致。
- 临界资源:共享资源(同⼀对象),⼀次仅允许⼀个线程使⽤,才可保证其正确性。
- 原⼦操作:不可分割的多步操作,被视作⼀个整体,其顺序和步骤不可打乱或缺省。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则的话就可能影响线程安全。
class TicketRunnable implements Runnable {
private int ticket = 100;
//每个窗⼝卖票的操作 窗口永远开启
@Override
public void run() {
while (true) {//有票可以卖
//出票操作
if (ticket > 0) {
//使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}
}
class ThreadSafe {
public static void main(String[] args) throws Exception {
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t, "窗⼝1");
Thread t2 = new Thread(t, "窗⼝2");
Thread t3 = new Thread(t, "窗⼝3");
//3个窗⼝同时卖票
t1.start();
t2.start();
t3.start();
}
}
结果为会出现不同的窗口卖不同的票,还会出现第0张的情况,这就是线程中使用全局变量,导致参数共享,在执行阶段没有做同步处理,导致线程不安全。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:
- 同步代码块
- 同步方法
- 锁机制
同步锁:对象的同步锁只是⼀个概念,可以想象为在对象上标记了⼀个锁。
- 锁对象 可以是任意类型。
- 多个线程对象 要使⽤同⼀把锁。
在任何时候,最多允许⼀个线程拥有同步锁,谁拿到锁就进⼊代码块,其他的线程只能在外等着(BLOCKED)。
(1)同步代码块
语法:
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原⼦操作)
}
class TicketRunnable implements Runnable {
private int ticket = 20;
Object lock = new Object();
@Override
public void run() {
while (true) {//有票可以卖
//出票操作
synchronized (lock){
if (ticket > 0) {
//使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}
}
}
class ThreadSafe {
public static void main(String[] args) throws Exception {
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t, "窗⼝1");
Thread t2 = new Thread(t, "窗⼝2");
Thread t3 = new Thread(t, "窗⼝3");
//3个窗⼝同时卖票
t1.start();
t2.start();
t3.start();
}
}
窗⼝2正在卖票:20
窗⼝2正在卖票:19
窗⼝2正在卖票:18
窗⼝2正在卖票:17
窗⼝2正在卖票:16
窗⼝2正在卖票:15
窗⼝2正在卖票:14
窗⼝2正在卖票:13
窗⼝2正在卖票:12
窗⼝2正在卖票:11
窗⼝2正在卖票:10
窗⼝2正在卖票:9
窗⼝2正在卖票:8
窗⼝2正在卖票:7
窗⼝2正在卖票:6
窗⼝2正在卖票:5
窗⼝2正在卖票:4
窗⼝2正在卖票:3
窗⼝2正在卖票:2
窗⼝2正在卖票:1
执行结果没有超卖现象,没有重复卖现象。
(2)同步方法:使⽤synchronized修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着。
语法:
synchronized 返回值类型 ⽅法名称(形参列表){
//对当前对象(this)加锁 // 代码(原⼦操作)
}
- 只有拥有对象互斥锁标记的线程,才能进⼊该对象加锁的同步⽅法中。
- 线程退出同步⽅法时,会释放相应的互斥锁标记。
- 对于⾮static⽅法,同步锁就是this。
- 如果⽅式是静态,锁是类名.class。
class TicketRunnable implements Runnable {
private int ticket = 20;
Object lock = new Object();
@Override
public void run() {
while (true) {//有票可以卖
sellTicket();
if(ticket<=0){
break;
}
}
}
/**
* 锁对象,谁调⽤这个⽅法,就是谁
* 隐含锁对象,就是this
* 静态⽅法,隐含锁对象就是TicketRunnable.class
*/
public synchronized void sellTicket(){
if (ticket > 0) {
//使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}
class ThreadSafe {
public static void main(String[] args) throws Exception {
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t, "窗⼝1");
Thread t2 = new Thread(t, "窗⼝2");
Thread t3 = new Thread(t, "窗⼝3");
//3个窗⼝同时卖票
t1.start();
t2.start();
t3.start();
}
}
(3)Lock
- JDK5加⼊,与synchronized⽐较,显示定义,结构更灵活。
- 提供更多实⽤性⽅法,功能更强⼤、性能更优越。
方法名 | 描述 |
---|---|
void lock()
|
获取锁,如锁被占⽤,则等待。
|
boolean tryLock()
|
尝试获取锁(成功返回true。失败返回false,不 阻塞)。
|
void unlock()
|
释放锁。
|
ReentrantLock: 重入锁,Lock接⼝的实现类,与synchronized⼀样具有互斥锁功能。
public class MyList {
//创建锁
private Lock lock = new ReentrantLock();
private String[] str = {"A", "B", "", "", ""};
private int count = 2;
public void add(String value) {
//当没有锁的时候,会出现覆盖的情况
str[count] = value;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + "添加了" + value);
}
public String[] getStr() {
return str;
}
public static void main(String[] args) throws InterruptedException {
MyList myList = new MyList();
Thread t1 = new Thread(() -> myList.add("hello"));
t1.start();
Thread t2 = new Thread(() -> myList.add("world"));
t2.start();
t1.join();
t2.join();
String[] str = myList.getStr();
for (String s : str) {
System.out.println("s:" + s);
}
}
}
运行结果:
Thread-0添加了hello
Thread-1添加了world
s:A
s:B
s:world
s:
s:
如果不加锁会出现覆盖,结果应该是[A,B,hello,world,""],此时线程1的操作覆盖了线程0的操作,所以结果为:[A,B,world,"",""]。下面是加锁的情况
public class MyList {
//创建锁
private Lock lock = new ReentrantLock();
private String[] str = {"A", "B", "", "", ""};
private int count = 2;
public void add(String value) {
lock.lock();
try {
str[count] = value;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + "添加 了" + value);
} finally {
lock.unlock();
}
}
public String[] getStr() {
return str;
}
public static void main(String[] args) throws InterruptedException {
MyList myList = new MyList();
Thread t1 = new Thread(() -> myList.add("hello"));
t1.start();
Thread t2 = new Thread(() -> myList.add("world"));
t2.start();
t1.join();
t2.join();
String[] str = myList.getStr();
for (String s : str) {
System.out.println("s:" + s);
}
}
}
运行结果:
Thread-0添加 了hello
Thread-1添加 了world
s:A
s:B
s:hello
s:world
s:
6.线程间的通讯
1.volatile
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
public class VolatileDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (flag) {
System.out.println("trun on");
flag = false;
}
}
}).start();
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!flag) {
System.out.println("trun off");
flag = true;
}
}
}).start();
}
}
线程1和线程2之间可以共享flag变量,做到相互通信一直循环输出。如果不使用volatile会造成死锁
public class VolatileDemo {
private static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("trun on");
flag = false;
}
}
}).start();
new Thread(() -> {
while (true) {
if (!flag) {
System.out.println("trun off");
flag = true;
}
}
}).start();
}
}
等待唤醒(等待通知)机制 :
等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒。
public class WaitDemo {
private static Object lock = new Object();
private static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
while (flag) {
try {
System.out.println("wait start .......");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait end ....... ");
}
}).start();
new Thread(() -> {
if (flag) {
synchronized (lock) {
if (flag) {
lock.notify();
System.out.println("notify .......");
flag = false;
}
}
}
}).start();
}
}
LockSupport:是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用unpark则为指定线程提供“许可”。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("唤醒parkThread");
LockSupport.unpark(parkThread);
}
static class ParkThread implements Runnable {
@Override
public void run() {
System.out.println("ParkThread开始执行");
LockSupport.park();
System.out.println("ParkThread执行完成");
}
}
}
管道输入输出流管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
public class Piped {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1){
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
@Override
public void run() {
int receive = 0;
try {
while ((receive = in.read()) != -1){
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}