一、基本概念
1、进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
2、线程是指进程中的一个执行流程,一个进程可以运行多个线程。比如java.exe进程可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其它线程一起共享分配给该进程的所有资源。
3、线程“同时”执行只是人的感觉,在线程之间实际上轮换执行。
4、进程在执行过程中拥有独立的内存单元,进程有独立的地址空间。一个进程中的多个线程共享进程的内存块。当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,极大的提高了程序的运行效率。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。
5、线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。
6、线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:
(1)一个指向当前被执行指令的指令指针。
(2)一个栈。
(3)一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值。
(4)一个私有的数据区。
7、多任务就是同一时刻运行多个程序的能力。每一个任务称为一个线程。线程是进程中负责执行程序的一个执行控制单元,线程负责程序的执行,而一个进程允许有多个控制单元,我们称为多线程。
8、Java编写程序都运行在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。
9、在Java中,每次程序运行至少启动2个线程,而且每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行。每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动一个进程。java本身具备了垃圾回收机制,所以每个java运行时至少会启动两个线程,一个main线程,另外一个是垃圾回收机制,JVM找到程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。一旦再创建一个新的线程,就产生一个新的调用栈。当main方法结束后,主线程运行完成,JVM进程也随即退出。
10、线程分为两类:用户线程和守候线程。守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。用户线程是独立存在的,不会因为其他用户线程退出而退出。默认情况下启动的线程是用户线程,通过setDaemon(true)将线程设置成守护线程,这个函数务必在线程启动前进行调用,否则会报java.lang.IllegalThreadStateException异常,启动的线程无法变成守护线程,而是用户线程。
总结:
一个进程里面,至少有一个线程。
多线程的优点:解决了我们需要同时运行多代码的问题。
缺点:线程太多会降低效率。
二、线程的定义
1、在Java中,“线程”指两件不同的事情:
(1)java.lang.Thread类的一个实例;
(2)线程的执行。
2、在 Java程序中,有两种方法创建线程:
(1)对 Thread 类进行派生并覆盖run方法;
(2)通过实现Runnable接口创建。
Java中线程是指使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
三、线程创建与启动的实例
1、使用析构函数(finalize)进行垃圾回收和主线程进行构造演示多线程
public class Student {
private String name;
Student(String name){
this.name = name;
System.out.println(name + "被构造了");
}
//回收对象的时候,由对象的垃圾回收器调用此方法
@Override
protected void finalize() throws Throwable {
System.out.println(name + "被回收了");
}
}
测试
public class Test {
public static void main(String[] args) {
new Student("胡井号");//匿名对象
new Student("张三");
new Student("李四");
new Student("王五");
System.gc();//运行垃圾回收器
System.out.println("执行完毕");
}
}
2、创建线程的第一种方式:
(1)继承Thread类
(2)重写Thread类里面的run方法
(3)创建线程的子类对象
(4)调用start方法启动线程
public class Dog extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());//返回当前正在执行的线程对象的名字
for (int i = 0;i<100;i++){
System.out.println("狗在跑");
}
}
public void shout(){
System.out.println(Thread.currentThread().getName());
for (int i = 0;i<100;i++){
System.out.println("汪汪汪");
}
}
}
测试
public class Test1 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.start();//调用start方法,开启线程,运行run方法
dog.shout();//调用shout方法的时候,开启的是主线程(main)
}
}
局限性:不能再继承别的类了
3、创建线程的第二种方法:
(1)定义类实现Runnable接口()
(2)实现接口里的run方法,也就是将线程要执行的任务封装在run方法中
(3)创建一个线程对象,然后将实现Runnable接口的类作为创建线程对象的构造方法的参数传入
(4)调用线程对象的start方法,启动线程
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
public class Dog implements Runnable { //实现Runnable接口
@Override
public void run() {
System.out.println("狗在跑~");
}
public void shout(){
System.out.println("汪汪汪~");
}
}
测试
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);//创建线程对象,把dog对象放到线程中
thread.start();//开启线程
dog.shout();
System.out.println("执行完毕");
}
}
4、创建线程的第三种方法:匿名内部类启动线程
public class Game {
public static void begin(){
new Thread(){ //创建匿名对象
@Override
public void run() {
super.run();
}
}.start();//启动线程
}
public static void main(String[] args) {
begin();//调用静态方法
}
}
5、线程安全问题:
(1)多线程操作共享数据
(2)操作共享数据的代码有多条
解决办法:同步代码块
好处:解决了线程安全问题
弊端:降低了效率,每次都要去判断同步锁(synchronized)
买票案例:
public class Ticket implements Runnable{
private int num = 100;//定义票数
Object object = new Object();//创建Object对象
@Override
public void run() {
try {
sale();//调用售票的方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sale() throws InterruptedException {
while (true) {
synchronized (object) {//同步锁
if (num > 0) {
Thread.sleep(100);//线程休眠100秒
//获得线程对象的名字,然后票数减1
System.out.println(Thread.currentThread().getName() + "卖了" + num--);
} else {
break;//不卖票的话需要停止,不然会陷入死循环
}
}
}
}
}
测试
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//Thread的构造函数用的是两个参数的,一个是要开启线程的对象和线程的名字
Thread thread1 = new Thread(ticket,"张三");
Thread thread2 = new Thread(ticket,"李四");
Thread thread3 = new Thread(ticket,"王五");
Thread thread4 = new Thread(ticket,"赵六");
Thread thread5 = new Thread(ticket,"胡井号");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
在买票过程中cpu会来回切换要执行的线程,当卖完票的时候,票数还没减的时候,又去执行另一个线程,会造成买到同一张票的线程安全问题,所以用完同步锁后,就可以执行完这个同步代码块的内容后才能去执行其他的线程
同步代码块和同步函数:
同步代码块的锁是任意对象,同步函数的锁是this
死锁常见的的情形之一:同步代码块的嵌套
例子:
public class MyLock {
public static final Object LOCKA = new Object();//创建锁A对象
public static final Object LOCKB = new Object();//创建锁B对象
}
public class DeathLock implements Runnable {
private boolean flag;
public DeathLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) { //如果为true就进入锁A和锁B
synchronized (MyLock.LOCKA) {
System.out.println(Thread.currentThread().getName() + "if的LOCKA");
synchronized (MyLock.LOCKB) {
System.out.println(Thread.currentThread().getName() + "if的LOCKB");
}
}
} else { //为false就进入锁B和锁A
synchronized (MyLock.LOCKB) {
System.out.println(Thread.currentThread().getName() + "else的LOCKB");
synchronized (MyLock.LOCKA) {
System.out.println(Thread.currentThread().getName() + "else的LOCKA");
}
}
}
}
}
测试
public class Test {
public static void main(String[] args) {
DeathLock d1 = new DeathLock(true);
DeathLock d2 = new DeathLock(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
6、线程调度:
(1)线程优先级设置
public class Talk implements Runnable{
private String name;
public Talk(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我叫" + name + i);
}
}
}
测试
public class Test {
public static void main(String[] args) {
Talk t = new Talk("董事长");
Talk a = new Talk("职员");
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(a);
thread1.setPriority(Thread.MAX_PRIORITY);//调用线程更改优先级的方法(最高级)
thread2.setPriority(Thread.MIN_PRIORITY);//调用线程更改优先级的方法(最低级)
thread1.start();
thread2.start();
}
}
(2)线程插队
public class Talk implements Runnable{
@Override
public void run() {
System.out.println("我在说话");
}
}
测试
public class Test {
public static void main(String[] args) throws InterruptedException {
Talk t = new Talk();
Thread thread = new Thread(t);
thread.start();
for (int i = 0;i<5;i++){
System.out.println("我是主线程");
if (i==3){
thread.join();//插队
}
}
}
}
(3)线程让步
public class Talk implements Runnable{
private String name;
public Talk(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
System.out.println("我叫" + name + i);
Thread.yield();//让步
}
}
}
测试
public class Test {
public static void main(String[] args) {
Talk t = new Talk("董事长");
Talk a = new Talk("职员");
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(a);
thread1.start();
thread2.start();
}
}
7、生产者消费者问题
资源类
public class Resource {
private String name;
private int id = 1;//编号
private boolean flag = false;//为true时生产,生产完把flag的值改为true时就能消费了,消费完把值改为false
public synchronized void set(String name){ //同步函数
if (!flag){ //如果为true
this.name = name + id;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
this.flag = true;//把默认的false变成true
id++;
notify();//唤醒单个线程
}else {
try {
this.wait();//线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void out(){
if (flag){
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
this.flag = false;
notifyAll();//唤醒所有线程
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者类
public class Producer implements Runnable{
private Resource r;//把资源类作为属性
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true){
this.r.set("烤鸭");//调用资源类的set方法
}
}
}
消费者类
public class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true){
this.r.out();//调用资源类的out方法
}
}
}
测试
public class Test {
public static void main(String[] args) {
Resource r = new Resource();//创建资源对象
Producer p = new Producer(r);//创建生产者对象
Consumer c = new Consumer(r);//创建消费者对象
//一个生产者,多个消费者
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
Thread t5 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}