1.什么是进程?
简单理解 就是 正在运行的程序就是进程;
每个程序有个独立的进程,而进程之间是相互独立存在的;
2.什么是线程?
进程 运行需要线程,进程运行可以有多个线程,但进程只要需要一个线程。
3.什么是多线程?
多线程就是串行和并行两个概念;
串行:就是单条线来执行的,前一个执行完才能执行后一个,
并行:多条线同时进行,同步执行任务;
比如在下载多个文件,串行就是先把前一个文件下载完,再下载后一个文件,这样下载效率慢。
并行就可以一次性同步下载多个文件,这样充分利用资源,可以提高下载效率。
4.如何使用线程
第一种继承Thread 方式 ;
public class Demo extends Thread {
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"---run线程---"+i);
}
}
}
public class TestThread {
public static void main(String[] args) {
Demo demo = new Demo();
demo.setName("线程1");
demo.start();
Demo demo1 = new Demo();
demo1.setName("线程2");
demo1.start();
}
}
Thread.currentThread().getName()是命名线程名,会有一个默认的线程名,没有给线程输入名称就会用默认名。
还有一种this.getName()也可以进行命名,但是不常用 ,
第二种实现Runnable接口;
public class Demo2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" --------"+i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
//创建一个线程任务
Demo2 demo2 = new Demo2();
Thread thread = new Thread(demo2,"线程一");
thread.start();
Thread thread2 = new Thread(demo2,"线程二");
thread2.start();
}
}
这两种方式 在写法上没什么太大的区别 ,用实现Runnable接口多一些,因为 继承Thread方式,子类一次只能继承一个父类,使用不太方便。
5.常用方法
1.休眠:public static viod sleep(long millis) 当前线程 主动休眠 millis 秒
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 20; i++) { try { //休眠 每隔一段时间 执行一次 Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } //获取线程名称 System.out.println(Thread.currentThread().getName()+"------"+i); }
2.放弃:public static viod yield()当前线程主动放弃时间片,回到就绪状态竞争下一次时间片
public static void main(String[] args) { MyThread myThred = new MyThread(); myThred.setName(" "); myThred.start(); //放弃 myThred.yield(); for (int i = 0; i < 20; i++) { System.out.println("main------"+i); } }
3.加入:public static void join() 允许其他线程加入到当前线程中,并其他线程执行完毕后,当前线程才会执行。
public static void main(String[] args) { MyThread myThred = new MyThread(); myThred.setName(" "); myThred.start(); try { myThred.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } for (int i = 0; i < 20; i++) { System.out.println("main------"+i); } }
4.优先级: 线程对象 .setPriority()
线程优先级分为1-10,默认是5,等级越高,表示获取cpu 概率越高
public static void main(String[] args) { MyThread myThred = new MyThread(); MyThread myThred2 = new MyThread(); myThred.setPriority(3); myThred.setName("111 "); myThred.start(); myThred2.setPriority(10); myThred2.setName("222 "); myThred2.start();}
5.守护线程: 线程对象.setDaemon(true);设置为守护线程。
线程有两类:用户线程(前台线程)和 守护线程(后台线程)
程序中前台线程执行完毕,后台线程也会自动结束;
public static void main(String[] args) { MyThread myThred = new MyThread(); MyThread myThred2 = new MyThread(); myThred.setDaemon(true); myThred.setPriority(3); myThred.setName("111 "); myThred.start(); myThred2.setPriority(10); myThred2.setName("222 "); myThred2.start();}
6.做两个小案例
卖票 A,B,C三个窗口各买100张票
public class Demo extends Thread {
private int ticket=100;
@Override
public void run(){
while (true){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张票还剩"+ticket+"张");
}
}
}
public class Demo extends Thread {
private int ticket=100;
@Override
public void run(){
while (true){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张票还剩"+ticket+"张");
}
}
}
A,B,C三个窗口 共同卖100张票
public class Demo2 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"买了一张票还剩"+ticket+"张");
}
}
}
public class TestRunnable {
public static void main(String[] args) {
//创建一个线程任务
Demo2 demo2 = new Demo2();
Thread thread = new Thread(demo2,"A");
thread.start();
Thread thread2 = new Thread(demo2,"B");
thread2.start();
Thread thread3 = new Thread(demo2,"C");
thread3.start();
}
}
第二个A,B,C三个窗口 共同卖100张票时 打印的出来的结果 出现了重复和超出的问题, 这些问题就是线程安全问题,下面来看看线程安全问题。
7.线程安全问题
多个线程共享一个资源时,可能会出现线程安全问题
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省
7.2如何保证线程安全性?
1.同步代码块
synchronized(临界资源对象){//对临界资源对象加锁
//代码 (原子操作)
}
2.
Lock lock= new ReentrantLock()
lock.lock()
//代码
lock.unlock();
public class arr {
private static String[] arr= new String[2];
private static int index=0;
private static Lock lock=new ReentrantLock();
public static void main(String[] args) {
//创建一个线程类 并为其指定任务
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
synchronized (arr){ //自动开启锁 和自动释放锁
if(arr[index]==null){
arr[index]="hello";
index++;
}
}
}
});
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();//手动开启锁
if(arr[index]==null){
arr[index]="world";
index++;
}
lock.unlock();//手动释放锁
}
});
//开启线程
thread.start();
thread2.start();
//先加执行完thread,thread2线程,再执行main主线程
try {
thread.join();
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Arrays.toString(arr));
}
}
这样就也能 解决 上边案例 的线程 重复,超出的问题
注意: 每个对象都有一个互斥锁标记, 用来分配给线程。
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块。
线程退出同步代码块,会释放相应的互斥锁标记