前言
进程与线程
进程:可以理解为可以独立运行的应用程序,
线程:进程中的控制单元,一个进程至少存在一个主线程,一个进程可以有多个线程,可以让一个程序同时执行多个任务。
并发与并行
并发:可以理解为一个CPU上同时执行多个任务,在逻辑上是同时发生,但并不是物理上的同时执行,因为一个CPU在某一时刻只能执行一个指令,通过快速切换的方式运行不同的指令达到,即指令的执行并不是在同一时刻发生的(当有多个线程进行操作时,如果只有一个CPU,不可能同时进行多个线程的操作,只能根据CPU的调度不同的时间段分配给不同的线程执行,即同一时刻不能执行多个指令)。
并行:可以理解为多个CPU同时执行多个任务,即多个指令可以在多个CPU上同一时刻执行(有多个CPU时,是可以同时执行不同的线程的,当一个CPU在执行一个指令时,另一个CPU可以执行另一个指令,两个线程可以互不抢占CPU的资源,即同一时刻可以执行指令多个)。
一、线程的创建
线程的创建常用的两种方式:继承Thread类、实现Runnable接口或者Callable接口。
- 继承Thread类创建线程:
特点:实现简单、不能继承其他类、不能共享同一资源(如成员变量)。
代码案例:
public class Thread01 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("MyThread-1");
myThread1.start();
MyThread myThread2 = new MyThread("MyThread-2");
myThread2.start();
}
}
//继承Thread
class MyThread extends Thread{
MyThread(String name){
super(name);
}
@Override
public void run() {
while (true){
System.out.println(this.getName()+"run ------->");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- Runnable 接口来创建线程:
特点:可以继承其他类、可以多个线程处理同一资源,一般采用该方式实现多线程。
代码案例:
public class T_Runnable {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable,"线程一");
t1.start();
Thread t2 = new Thread(myRunnable,"线程二");
t2.start();
}
}
//实现Runnable
class MyRunnable implements Runnable{
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+":execute..............");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用Lambda表达式:
public class T_Lambda {
public static void main(String[] args) {
//jdk8,只有一行时可以省略{}
Thread t1 = new Thread(() -> {
while (true){
System.out.println(Thread.currentThread().getName()+"run 执行了。。。");
}
});
t1.start();
}
}
二、生命周期及状态转换。
生命周期可以分为五个阶段:新建状态(New)、就绪状态((Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Terminated),线程的不同状态表明了线程当前正在进行的活动。
①新建状态:
使用 new 关键字创建对象时处于新建状态,直到调用start()。
②就绪状态:
调用start()后,进入就绪状态,进入就绪队列,等待jvm调度。
③运行状态:
获得cpu资源,执行run()处于运行状态,此时状态可变成阻塞、就绪、死亡状态。
④阻塞状态:
执行sleep(睡眠)、suspend(挂起)等方法,失去占用资源后,该线程从运行状态进入阻塞状态。睡眠时间到达可重新进入就绪状态。等待阻塞:执行 wait() 方法,同步阻塞:获取 synchronized 同步锁失败,其他阻塞:发出了 I/O 请求、sleep() 、join() 等。
⑤死亡状态::
run()完成或者发生异常。
三、线程优先级
优先级越高的线程获得CPU执行的机会越大,越低则反之越小,线程的优先级用1~10表示,越大优先级越高,但java代码中不能通过调优先级去调度线程的先后执行的顺序,因为这是CPU进行调度的。
Thread中优先级的静态变量:
①static int MAX_PRIORITY (最高优先级,值为10)
②static int MIN_PRIORITY (最低优先级,值为1)
③static int NORM_PRIORITY (普通优先级,值为5)
使用案例:
public class Ex1 {
public static void main(String[] args) {
//优先级
Thread min = new Thread(new MinPriority(),"优先级低的线程");
Thread max = new Thread(new MaxPriority(),"优先级高的线程");
max.setPriority(Thread.MAX_PRIORITY);
min.setPriority(Thread.MIN_PRIORITY);
min.start();
max.start();
}
}
class MaxPriority implements Runnable{
@Override
public void run() {
for (int x = 0 ; x < 10; x++){
System.out.println(Thread.currentThread().getName()+"正在输出"+x);
}
}
}
class MinPriority implements Runnable{
@Override
public void run() {
for (int x = 0 ; x < 10; x++){
System.out.println(Thread.currentThread().getName()+"正在输出"+x);
}
}
}
四、线程休眠。
使用静态方法sleep(long millis)可以让线程暂停一段时间,进入休眠状态。
使用案例:
public class Ex2_Sleep {
public static void main(String[] args) {
new Thread(new TSleep()).start();
for (int i = 0; i < 10; i++){
System.out.println("main ....." + i );
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TSleep implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println("TSleep ....." + i );
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、线程让步
线程让步,是指某个线程在执行到某种情况下,把CPU资源让给其他线程执行。通过调用yield()方法实现。
使用案例:
public class Ex3_yield {
public static void main(String[] args) {
YieldThread y1 = new YieldThread("线程一");
YieldThread y2 = new YieldThread("线程二");
y1.start();
y2.start();
}
}
class YieldThread extends Thread{
public YieldThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0 ;i < 10; i++){
System.out.println(Thread.currentThread().getName()+"----"+i);
if (i==3){
System.out.println(Thread.currentThread().getName()+"让步");
Thread.yield();
}
}
}
}
六、线程插队
当某个线程调用其他线程的join()方法时,被调用的线程将阻塞,直到调用join()方法的线程执行完之后,被调用的线程才会继续执行。
使用案例:
public class Ex4_Join {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadJoin(),"线程一");
t1.start();
for(int i = 0 ;i< 10 ; i++){
System.out.println(Thread.currentThread().getName() + "输入" + i);
if (i == 2){
t1.join();
}
Thread.sleep(500);
}
}
}
class ThreadJoin implements Runnable{
@Override
public void run() {
for(int i = 0 ;i< 10 ; i++){
System.out.println(Thread.currentThread().getName() + "输入" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程安全:
在多个线程同时调用共享资源时,可能会产生线程安全问题。
- 同步代码块处理线程安全问题。
使用synchronized 修饰同步代码块。
//加入同步代码块
public class Ex6_Security {
public static void main(String[] args) {
SeThread1 s1 = new SeThread1();
new Thread(s1,"线程一").start();
new Thread(s1,"线程二").start();
new Thread(s1,"线程三").start();
new Thread(s1,"线程四").start();
}
}
class SeThread1 implements Runnable{
private int ticket = 1000;
private Object lock = new Object();
@Override
public void run() {
while (true){
synchronized (lock){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "卖出第:"+ ticket-- +"张票");
}else {
break;
}
}
}
}
}
- 同步方法块处理线程安全问题。
使用synchronized 修饰同步方法。
//加入同步方法
public class Ex7_Security {
public static void main(String[] args) {
SeThread2 s1 = new SeThread2();
new Thread(s1,"线程一").start();
new Thread(s1,"线程二").start();
new Thread(s1,"线程三").start();
new Thread(s1,"线程四").start();
}
}
class SeThread2 implements Runnable{
private static int ticket = 1000;
private Object lock = new Object();
@Override
public void run() {
while (true){
checkTicket();
if (ticket <= 0){
break;
}
}
}
//同步方法,同步方法的锁对象就是当前类的一个对象,这个对象使用的就是this
//如果是静态同步方法,锁对象为:当前类名.class ---> 当前类所对应的字节码文件对象。
//加入锁代码效率会降低
public static synchronized void checkTicket(){
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第:"+ ticket-- +"张票");
}
}
}
- 死锁问题
死锁问题一般出现在嵌套锁的场景(所以应该尽量避免使用嵌套锁),当两个线程同时等待对方释放锁的时(双方都拿着对方需要的锁互不让步,最后谁也解不开(●’◡’●)…),会使程序停滞,这就是是死锁现象。
//死锁
public class Ex8_Security {
public static void main(String[] args) {
SeThread3 s1 = new SeThread3(true);
SeThread3 s2 = new SeThread3(false);
new Thread(s1,"线程一").start();
new Thread(s2,"线程二").start();
}
}
class SeThread3 implements Runnable{
static Object lock1 = new Object();
static Object lock2 = new Object();
private boolean flag;
SeThread3(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag){
while (true){
synchronized (lock1){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"if --- lock1--->");
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"if ---lock2--->");
}
}
}
}else {
while (true){
synchronized (lock2){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"else---lock2--->");
synchronized (lock1){
System.out.println(Thread.currentThread().getName()+"else----lock1--->");
}
}
}
}
}
}