一:基本概念:程序,进程,线程
程序: 是完成特定任务,用某种语言编写的一组指令集合,即指一段静态的代码。
进程:是程序的一次执行过程,或是正在运行的一个程序。
线程:进程可以进一步细分为线程,是一个程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计算器,多个线程共享同一个进程中的结构:方法区和堆
二:线程的创建
方法一:继承类Thread,遍历100以内的偶数
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
if (i % 2 == 0) {
System.out.println(getName() + "--" + i);
}
}
}
public static void main(String []args){
MyThread m1 =new MyThread();
//启动线程,调用run()方法
m1.start();
}
}
方法二:实现Runnable接口,遍历100以内的偶数
public class RunnableTest implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public static void main(String[] args) {
RunnableTest run = new RunnableTest();
Thread t1 =new Thread(run);
//启动线程,调用run()方法
t1.start();
}
}
两种方式的比对:
开发中优选选择实现Runnable接口方式
原因: 1. 实现的方式没类的单继承局限性
2.实现的方式更适合来处理多个线程共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
目前两种方式,要想启动线程,都是调用Thread类的start()
三 :Thread类的常用方法:
1.start():启动当前线程,调用当前线程的run()
2.run():通常需要重写Threa类中的此方法,将创建线程要执行的操作声明在此方法中
3.currentThread(): 静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():释放当前cpu的执行权
7.join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完以后,线程a结束阻塞状态
8.sleep(long millitime):让当前线程睡眠,指定的millitime:毫秒,在指定的millitime时间,线程进入阻塞状态
9.isAlive():判断当前线程是否存活
四 :线程的生命周期:
新建:当一个Thread类或其子类的对象被声明和创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待cpu时间片,此时它已经具备的运行的条件,只是没分配到cpu资源
运行:当就续的线程被调度并获取得cpu资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
五 :线程的同步:
1)卖票,线程安全问题的引出:
public class Windows extends Thread{ private static int tickets=100; @Override public void run() { while (true) { if (tickets > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票的票号为:" + tickets); tickets--; } else { break; } } } public static void main(String[]args){ Windows w1 =new Windows(); Windows w2 =new Windows(); Windows w3 =new Windows(); w1.setName("窗口一:"); w2.setName("窗口二:"); w3.setName("窗口三:"); w1.start(); w2.start(); w3.start(); } }
4执行结果:
窗口二:卖票的票号为:100
窗口一:卖票的票号为:100
窗口三:卖票的票号为:98
窗口三:卖票的票号为:97
窗口一:卖票的票号为:97
2)线程安全问题的解决:(继承类)同步代码块,关键字:synchronized
public class Windows extends Thread{
private static int tickets=100;
@Override
public void run() {
while (true) {
//同步监视器
synchronized (Windows.class) {
if (tickets > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票的票号为:" + tickets);
tickets--;
} else {
break;
}
}
}
}
public static void main(String[]args){
Windows w1 =new Windows();
Windows w2 =new Windows();
Windows w3 =new Windows();
w1.setName("窗口一:");
w2.setName("窗口二:");
w3.setName("窗口三:");
w1.start();
w2.start();
w3.start();
}
}
3)线程安全问题的解决:(实现接口)同步代码块,关键字:synchronized
public class Windows2 implements Runnable {
private int tickets=100;
@Override
public void run() {
while (true) {
//同步监视器
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + tickets);
tickets--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
Windows2 win =new Windows2();
Thread t1 =new Thread(win);
Thread t2 =new Thread(win);
Thread t3 =new Thread(win);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
4)线程安全问题的解决:(继承类)同步方法,关键字:synchronized
public class SynchronizedTest1 extends Thread {
private static int tickets=100;
@Override
public void run() {
while (true) {
show();
if(tickets<=0){
break;
}
}
}
private static synchronized void show() {
if (tickets > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票的票号为:" + tickets);
tickets--;
}
}
public static void main(String[]args){
SynchronizedTest1 w1 =new SynchronizedTest1();
SynchronizedTest1 w2 =new SynchronizedTest1();
SynchronizedTest1 w3 =new SynchronizedTest1();
w1.setName("窗口一:");
w2.setName("窗口二:");
w3.setName("窗口三:");
w1.start();
w2.start();
w3.start();
}
}
5)线程安全问题的解决:(实现接口)同步方法,关键字:synchronized
public class SynchronizedTest2 implements Runnable {
private int tickets=100;
@Override
public void run() {
while (true) {
show();
if(tickets<=0){
break;
}
}
}
public synchronized void show(){
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + tickets);
tickets--;
}
}
public static void main(String[] args) {
SynchronizedTest2 win =new SynchronizedTest2();
Thread t1 =new Thread(win);
Thread t2 =new Thread(win);
Thread t3 =new Thread(win);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
总结:
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码--不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如ticktet
3.同步监视器,俗称锁,任何一个类的对象,都可以充当锁, 要求:多个线程必须共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
注:
同步的方式,解决了线程的安全问题---好处
操作同步代码时,只能有一个线程参与,其他线程等待相当于是一个单线程的过程,效率低。
六 :线程的通信:
1.线程通信的主要方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒
优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
2.说明:
》wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
》wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
》wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
》
3.例题:线程交替打印数字:
public class CommunicationTest { public static void main(String[] args) { Number number =new Number(); Thread t1= new Thread(number); Thread t2= new Thread(number); t1.setName("线程一:"); t2.setName("线程二:"); t1.start(); t2.start(); } } class Number implements Runnable { private int num = 1; @Override public void run() { synchronized (this) { while (true) { notifyAll(); if (num <= 100) { System.out.println(Thread.currentThread().getName() + "打印的数字为:" + num); num++; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } }
4.面试题: sleep()和wait()异同:
相同点:
一旦执行方法,都可以使当前的线程进入阻塞状态
不同点:
1)两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用,wait使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
七 :创建线程的另外两种方式:
方式一:实现Callable接口:
例题1: 实现打印100以内的偶数并求和:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableTest { public static void main(String[] args) { NumberTest number =new NumberTest(); FutureTask futureTask = new FutureTask(number); new Thread(futureTask).start(); Object obj = null; try { obj = futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(obj); } } class NumberTest implements Callable{ @Override public Object call() throws Exception { int sum=0; for(int i=0;i<=100;i++){ if(i%2==0){ System.out.println(Thread.currentThread().getName()+"-"+i); sum+=i; } } return sum; 说明:如何理解实现Callable接口方式创建多线程比实现Runable接口创建方式强大? 》call() 可以返回值的 》call()可以抛出异常,被外面的操作捕获,获取异常的信息 》Callable是支持泛型的 方式二:线程池: 例题1: 实现打印100以内的偶数并求和: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class NumberThread implements Runnable{ @Override public void run() { for(int i=0;i<=100;i++){ if (i % 2 == 0) { System.out.println(Thread.currentThread().getName()+"-"+i); } } } } public class ThreadPool { public static void main(String[] args) { //创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //启动线程 executorService.execute(new NumberThread()); } }
使用线程池的好处:
》提高响应速度(减少创建新线程的时间)
》降低资源消耗(重复利用线程池中线程,不需要每次都创建)
》便于线程管理