多线程: 多执行绪,同时执行
进程:实际上可以理解为程序,但有的程序可以有多个进程,操作系统可以同时运行多个进程,这样的操作多进程操作
线程:程序运行的执行路径(执行绪)
JVM是多线程的
实现多线程的方式如下:
思路一:
1.定义线程类继承Tread并重写方法
2.重写run()方法,在run方法中写要执行的程序
3.创建线程类对象
4.启动线程,自动调用方法(用线程对象引用调用start()方法)
代码实现过程如下:
// 定义线程类继承Tread并重写方法
public class MyThread extends Thread{
// 重写run()方法,在run方法中写要执行的程序
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Test {
// 创建线程类对象
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 启动线程
thread1.start();
thread2.start();
}
}
此方式还可以使用匿名内部类实现:
public class Test {
public static void main(String[] args) {
// 用匿名内部类的方式创建线程对象
new MyThread().start();
new MyThread().start();
}
}
每个线程都有优先级,默认优先级都是5(线程优先级是从1到10的整数)
线程加入:public final void join() throws InterruptedException
使用线程加入的线程,理论上其他线程需要等被加入线程执行完毕后再执行
线程礼让:public static void yield()
暂停当前正在执行的线程对象,并执行其他线程
线程中断:public void interrupt()
中断线程的 wait join sleep 相当于唤醒的意思
线程的生命周期:
新建状态:线程被创建,有执行资格
就绪状态:有执行资格,没有cpu使用权
执行状态:有执行资格,有cpu使用权——> 阻塞状态
消亡状态:线程消亡,既没有执行资格,也没有执行权
阻塞状态:sleep()/wait()/IO操作阻塞操作/加锁 ———>就绪状态 唤醒/唤醒/阻塞操作结束/解锁
守护线程:public final void setDaemon()
被守护线程随着守护线程额停止而停止运行
思路二:
1.定义一个类,实现Runnable 接口
2.重写run()方法
3.创建实例对象 在创建线程的对象的时候,传入参数,还可以直接在第二个参数创建名字
4.分配实例对象(创建一个线程对象最为这个线程对象的构造方法参数传入)
5.开启线程
代码实现:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Test {
public static void main(String[] args) {
// 创建试了对象
MyRunnable myRunnable = new MyRunnable();
// 分配 实例对象
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
// 启动线程
thread1.start();
thread2.start();
}
}
此方法还可以使用匿名内部类实现:
public class Test {
public static void main(String[] args) {
// 使用匿名内部类的方式创建线程对象
new Thread(new MyRunnable(){
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}).start(); // 开启线程
}
}
总结:实现runnable接口比继承Thread类好,因为可以在实现接口的同事继承其他的类,而如果继承了Thread类就不能再继承其他的类
这样将run方法中的业务与线程分离
synchronize:同步
使用同步代码块将要同步的代码包裹起来,该代码块需要一个对象作为参数,这个参数是同步代码块的锁由这个锁去判断是否是同一个代码块
锁的创建不能在run()方法内,因为如果放在run方法中,则每个线程都有自己的锁,与其他线程的锁不同,所以锁应该放在成员变量的位置(即在成员位置创建锁的参数对象)
代码实现过程:
1.让共享数据事项runnable接口
2.创建多线程对象,传入相同的共享数据对象
3.开启线程验证
public class Ticket implements Runnable {
// 必须要将共享的属性放在成员的位置作为共享数据
int num = 100;
Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) { //此同步代码块不能包括while循环,因为将导致最先获取执行权线程的死循环
try {
Thread.sleep(10); // 让线程睡眠10ms验证数据的同步性
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"正在销售第"+num+"张票");
num--;
}
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
// 创建共享 数据的对象
Ticket ticket = new Ticket();
// 创建线程对象
Thread thread = new Thread(ticket,"窗口1");
Thread thread2 = new Thread(ticket,"窗口2");
// 开启线程
thread.start();
thread2.start();
}
}
同步方法: 让方法本身同步,使用的锁为调用这个方法的对象this
格式:在方法前加 synchronize 关键字
多个线程跑的都是自己的run()方法,但是共享着run方法中的某些数据
总结:多线程可以保证共享数据的唯一性,可以通过让共享数据的放在成员变量的位置,或者只创建共享数据的一个实例对象来保证数据的唯一性
注意:synchronize 同步代码块只能使用同一个对象才能达到同步的功能