1.几个概念
1.程序:指唯一完成某个任务,用某种编程语言编写的一组指令的集合
2.进程:指程序的一次执行过程,例如我们启动了qq,word等,同时可以打开多个qq,就是说一个程序可以有多个进程
3.线程:进程的进一步细分,是一个程序内部执行的一条路径,一个进程可以有多个线程,一个线程就是一个指令,cpu调度的最小单位,由cpu一条一条的执行。一个进程同时执行多个线程,那么就是多线程。
4.并行:多个cpu同时执行多个任务
5.并发:一个cpu同时执行多个任务
2.多线程的创建
1.继承Thread类
1.步骤
1.让某个类成为Thread类的子类
2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
3.创建Thread类的对象后,调用start()方法,启动线程
class 线程类 extends Thread{
public void run(){
线程的任务
}
}
public static void main(String[] args){
线程类 线程类对象 = new 线程类();
线程类对象.start();
}
public class Windows extends Thread {
private int ticket=100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"正在卖第"+(101-ticket));
ticket--;
}
}
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w,"窗口1");
Thread t2 = new Thread(w,"窗口2");
Thread t3 = new Thread(w,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.注意:
如果我们要通过继承 Thread 类去实现多线程,那么我们就需要重写 Thread 类中的 run方法,然后去构建继承了 Tihread 类的对象,最后通过调用 start 方法去开启多线程。如果直接调用 run 方法就相当于在主线程 main 中调用一个类的普通的方法,并不会重新开启一个线程。
对于线程的开启我们还需要注意的是一个线程类只能调用一次 start 方法否则会抛出IllegalThreadStateException 异常。
3.Thread类常用方法
start():1.启动当前线程 2.调用线程中的 run 方法
run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方法中
currentThread():静态方法,返回执行当前代码的线程
getName():获取当前线程的名字
setName():设置当前线程的名字
yield():主动释放当前线程的执行权
join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
stop():过时方法。当执行此方法时,强制结束当前线程。
sleep(long millitime):线程休眠一段时间(毫秒)
2.实现Runable接口
class 线程类 implements Runable{
public void run(){
线程的任务
}
}
public static void main(String[] args){
线程类 线程类对象 = new 线程类();
线程类对象.start();
}
public class Windows implements Runnable{
private int ticket=100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"正在卖第"+(101-ticket));
ticket--;
}
}
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w,"窗口1");
Thread t2 = new Thread(w,"窗口2");
Thread t3 = new Thread(w,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
3.实现Callable接口
Callable与Runable类似,只是Callable的call方法有返回值
FutureTask可以用于接收Callable对象
FutureTask还提供了一个get方法用于拿取返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Model4 {
public static void main(String[] args) {
Th1 t = new Th1();
FutureTask futureTask = new FutureTask(t);
FutureTask futureTask2 = new FutureTask(t);
new Thread(futureTask).start();
new Thread(futureTask2).start();
}
}
class Th1 implements Callable {
private int num = 1;
@Override
public Object call() throws Exception {
while (true){
synchronized (this){
notify();
if (num <= 20){
System.out.println(Thread.currentThread().getName()+"--->"+num);
num++;
wait();
}else {
break;
}
}
}
return null;
}
}
3.线程的调度模式
cpu会给线程分配时间片,一但线程得到了时间片,他就可以执行,哪个线程抢到时间碎片,哪个就先执行,没有抢到时间片的就处于等待状态。
多线程的调度方式有两种即分时调度模式和抢占调度模式,其中分时调度模式是指让所有线程轮流获得 cpu 的使用权,并且分配的使用时间是平均的,而抢占模式则是指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
3.1线程的周期
4.线程安全
前面我们去实现了抢票案例,但是我们发现窗口一、窗口二、窗口三在售卖同一张票,造成这种现象主要是一个线程还没结束,另外一个线程就参与进来,从而造成三个窗口操作同一张票从而出现重票。
对于上述三个窗口售票异常我们也可以称之为线程安全问题,也就是当多个线程去操作共享数据的时候出现了共享数据的冲突,此时线程时不安全的。
4.1使用线程安全类
4.2Synchronized同步锁
这个关键字可以修饰方法或代码块。
synchronized(锁对象){
代码
}
修饰方法:写在方法的返回值前。这样该方法就称为同步方法,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。
public synchronized void fun(){
//要同步的代码
}
//同步代码块
public class Model8 {
public static void main(String[] args) {
WindowSal sal = new WindowSal();
Thread thread1 = new Thread(sal,"窗口一");
Thread thread2 = new Thread(sal,"窗口二");
Thread thread3 = new Thread(sal,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class WindowSal implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
synchronized (this){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"正在售卖第 "+(100-ticket+1)+"张票"+"还剩下:"+(ticket-1)+"张票。");
ticket--;
}else {
break;
}
}
}
}
}
//同步方法
public class Windows {
public static void main(String[] args) {
WindowSal sal = new WindowSal();
Thread thread1 = new Thread(sal,"窗口一");
Thread thread2 = new Thread(sal,"窗口二");
Thread thread3 = new Thread(sal,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class WindowSal implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
sal();
if (ticket<=0){
break;
}
}
}
private synchronized void sal(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"正在售卖第"+(100-ticket+1)+"张票"+"还剩下:"+(ticket-1)+"张票。");
ticket--;
}else {
return;
}
}
}
Synchronized特点
1)原子性,里面的代码不可分割,一起执行,一起不执行
2)有序性:被包裹的代码在多线程的条件下,只能由一条线程访问
3)可见性:每次操作其中的数据时,操作的是副本,而不是本体
4.3Lock
Lock();加锁
trylock(long 时间,时间单位);跟Lock();一样,加入了时间,但是有返回值,规定时间内加锁成功返回true,失败返回flase
unLock();释放锁
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Model3 {
public static void main(String[] args) {
T2 t1 = new T2("线程一");
T2 t2 = new T2("线程二");
t1.start();
t2.start();
}
}
class T2 extends Thread{
private static Lock lock = new ReentrantLock();
private static Condition c1 = lock.newCondition();
private static int num = 1;
T2(String name){
super(name);
}
@Override
public void run() {
while (true){
try {
lock.lock();
c1.signal();
if (num <= 20){
System.out.println(getName()+"--->"+num);
num++;
c1.await();
}else {
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
线程通信:利用wait()、join()、sleep()、notify()等方法实现线程中的通信
wait()-----使当前线程进入阻塞状态,并且释放锁,wait()需要抛出一个异常InterruptedException
notify()-----用于唤醒另外一个被wait()的线程。notifyall()----用于唤醒所有wait()线程。
wait()和notify()需要放在Synchronized同步方法或者代码块里面,通过锁对象调用wait()和notify()
wait();和sleep();区别
1.sleep();不会释放锁,wait();会释放
2.sleep();到时间自动苏醒,wait();需要手动唤醒
public class Tongxin {
public static void main(String[] args) {
Tt tt1 = new Tt("线程一");
Tt tt2 = new Tt("线程二");
tt1.start();
tt2.start();
}
}
class Tt extends Thread {
private static int num = 1;
private static Object o = new Object();
public Tt() {
}
public Tt(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (o) {
o.notify();//-----notify()用于唤醒另外一个被wait()的线程
if (num <= 20) {
System.out.println(getName() + num);
num++;
try {
o.wait();// ---使当前线程进入阻塞状态,并且释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
Lock和Synchronized区别
1、Synchronized是关键字,Lock是一个接口
2、Synchronized的加锁和释放锁是自动的,Lock则相反
3、Lock可以监听到它的锁状态
4、Synchronized会造成死锁,Lock则不会
4.4死锁
如有两个人吃西餐,必须要有刀和叉才能吃饭,只有一副刀叉。
如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己的,
这时就会造成死锁的局面,既不结束,也不继续。
造成死锁的原因:Synchronized使用不当
解决方法:1.改变加锁顺序 2.使用Lock锁
5.线程池
线程池是一种基于池化技术思想来管理线程的工具。在线程池中维护了多个线程,由线程池统一的管理调配线程来执行任务。通过线程复用,减少了频繁创建和销毁线程的开销。