1. 创建多线程
进程: 正在执行的程序作为一个进程,进程负责内存空间的划分
单核的CPU在一个时间只能执行一个应用程序,各个应用程序在抢CPU资源
* 线程 Thread : 任何一个java程序,jvm在运行的时候都会创建一个main线程执行main方法中所有的代码
* 一个java 应用程序至少有 2 个线程 jvm 创建的 一个 主线程 是负责 main 方法代码的执行,一个是垃圾回收器线程
*
创建新执行线程方法一:
1. 将类声明为 Thread 的子类。
2. 该子类应重写 Thread 类的 run 方法。? 目的: 自定义线程的任务代码就写在run方法中,自定义线程负责了 run 方法
start 方法开启线程
* 一个线程一旦开启,那么线程会执行 run 方法中的代码, run 方法千万不能直接调用,直接调用run方法 就相当于调用一个普通方法而已,并没有开启新线程
public class test_1 extends Thread { // 继承 Thread 类
@Override
public void run() {
for(int i=0; i<5; i++) {
System.out.println("自定义线程: " + i);
}
}
public static void main(String args[]) {
test_1 test = new test_1();
// test.run(); // 直接调用run方法 就相当于调用一个普通方法而已,并没有开启新线程
test.start();
for(int i=0; i<3; i++) {
System.out.println("main线程: " + i);
}
}
}
******************** output *****************************
main线程: 0
自定义线程: 0
main线程: 1
自定义线程: 1
main线程: 2
自定义线程: 2
创建新执行线程方法二:
* 1. 自定义一个类实现runnable接口
* 2. 实现runnable接口的run方法,把自定义线程的任务定义在run方法上
* 3. 创建runnable 实现类对象
* 4. 创建 Thread 类的对象,并且把Runnable实现类的对象作为实参传递。
* 5. 调用Thread 对象的start 方法开启一个线程。
* 问题一: Runnable实现类的对象是线程对象吗?
* 不是, 只是实现了Runnable 接口的对象而已。 只有Thread 或者 Thread 子类才是线程对象。
* 问题二: 为什么 把Runnable实现类的对象作为实参传递给 Thread 类的对象
* 作用就是把Runnable实现类的对象的run方法
* 源码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
推荐使用第二种: 实现 runnable 接口的, 因为 java单继承,多实现。
public class test_1 implements Runnable{ // 实现runnable接口
public static void main(String[] args) {
test_1 pTest_1 = new test_1(); // 创建runnable 实现类对象
//创建 Thread 类的对象,并且把Runnable实现类的对象作为实参传递
Thread thread = new Thread(pTest_1, "付祖贤女侠");
thread.start(); // 调用Thread 对象的start 方法开启一个线程
/*for(int i =0; i<5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
} */
}
@Override
public void run() {
/*for(int i =0; i<5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
} */
System.out.println("this: " + this);
System.out.println("当前线程: " + Thread.currentThread()); // 自定义线程的任务定义在run方法上
}
}
2. 线程常用的方法
线程常用的方法:
* Thread(String name) 初始化进程的名字
* getName() 返回线程的名字
* setName(String name) 设置线程对象名
* sleep() 线程睡眠指定的毫秒数, !!!静态方法!!!,哪个线程执行了这个代码,那么就是哪个线程睡眠。
* getPriority() 返回当前线程对象的优先级,默认线程的优先级是5
* setPriority(int newPriority) 设置线程的优先级 1~10,10为最大的优先级
* currentThread() 返回CPU正在执行的线程的对象,!!!静态方法!!!!
public class test_1 extends Thread {
public test_1 (String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<5;i++) {
System.out.println(this.getName()+ ": " + i);
// 为什么在这里不能抛出异常,只能捕获?? 子类抛出异常必须小于或等于父类异常
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} */
}
}
public static void main(String args[]) throws Exception {
// test_1 hh = new test_1();
// System.out.println("线程名称: " + hh.getName());
test_1 hhhh = new test_1("付祖贤");
// System.out.println("线程名称2: " + hhhh.getName());
// hhhh.sleep(1000); // 静态方法,是主线程在睡眠,因为这句代码是主线程执行
hhhh.setName(" 女侠 ");
hhhh.setPriority(10); // 优先级的数字越大,优先级越高,优先级的范围为: 1~10
hhhh.start();
for(int i=0;i<5;i++) {
System.out.println(Thread.currentThread().getPriority()+ ": " + i);
}
Thread mainthread = Thread.currentThread();
System.out.println("main: " + mainthread.getName());
// 线程的优先级默认为5
System.out.println("自定义的线程优先级: " + hhhh.getPriority());
System.out.println("主线程优先级: " + Thread.currentThread().getPriority());
}
}
************************* output *************************************
5: 0
5: 1
女侠 : 0
5: 2
女侠 : 1
5: 3
女侠 : 2
5: 4
女侠 : 3
main: main
女侠 : 4
自定义的线程优先级: 10
主线程优先级: 5
3. 线程安全问题
什么情况下才可能出现线程安全问题???
* 1. 存在两个或者两个以上的线程对象,而且线程之间共享一个资源
* 2. 有多个语句操作共享资源
*
解决方法: sun提供了线程同步机制, java线程同步机制方式:
方式一: 同步代码块, 格式:
synchronzied(锁对象){ 需要被同步的代码...... }
同步代码块要注意的事项:
* 1. 任意的一个对象都可以作为锁对象。
* 2. 在同步代码块中sleep方法,并不会释放锁对象。
* 3. 只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率的
* 4. 多线程操作的锁对象必须是唯一共享的,否则无效
例子: 模拟3个窗口同时在售100张票
class Saleticket extends Thread{
static int num = 1000; // 静态的成员变量,该数据在每个对象都会维护一份数据
static Object object = new Object(); // 用static修饰!
public Saleticket(String num) {
super(num);
}
@Override
public void run() {
while(true) {
synchronized (object) { // 这个对象也得用static修饰!!!!
// synchronized ("锁对象") { // 这样也可以,字符串一旦创建,就不会再次创建
// synchronized (new String ("锁对象")) { // 这样也不行!!!
if(num>0) {
System.out.println(Thread.currentThread().getName() + "售出了第 " + num + " 号票");
num--;
}else {
System.out.println("售罄了......");
break;
}
}
}
}
}
public class test_1 extends Thread{
public static void main(String[] args) {
Saleticket sale1 = new Saleticket("第1窗口");
Saleticket sale2 = new Saleticket("第2窗口");
Saleticket sale3 = new Saleticket("第3窗口");
sale1.start();
sale2.start();
sale3.start();
}
}
********************** output ************************************
......
第1窗口售出了第 979 号票
第1窗口售出了第 978 号票
第1窗口售出了第 977 号票
第3窗口售出了第 976 号票
第3窗口售出了第 975 号票
第3窗口售出了第 974 号票
.....
第3窗口售出了第 1 号票
售罄了......
售罄了......
售罄了......
方式二: 同步函数 ,使用synchronized修饰一个函数
注意事项:
* 1. 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁对象是当前函数所属类的字节码文件( class 对象)
* 2. 同步函数的锁对象
class Saleticket extends Thread{
static int num = 6; // 静态的成员变量,该数据在每个对象都会维护一份数据
static Object object = new Object();
public Saleticket(String num) {
super(num);
}
@Override
public synchronized void run() { // 同步函数 ,使用synchronized修饰一个函数
while(true) {
// synchronized (object) {
if(num>0) {
System.out.println(Thread.currentThread().getName() + "售出了第 " + num + " 号票, 还剩下: " + (num-1) + "张");
num--;
}else {
System.out.println("售罄了......");
break;
}
// }
}
}
// 静态的函数-----》 函数所属类的字节码文件 ------》 Saleticket.class
public static synchronized void getnum() { // 一个线程全做完
}
}
public class test_1 extends Thread{
public static void main(String[] args) {
Saleticket sale1 = new Saleticket("第1窗口");
Saleticket sale2 = new Saleticket("第2窗口");
sale1.start();
sale2.start();
}
}
************************** output ******************************
第1窗口售出了第 6 号票, 还剩下: 5张
第2窗口售出了第 6 号票, 还剩下: 5张
第1窗口售出了第 5 号票, 还剩下: 4张
第2窗口售出了第 4 号票, 还剩下: 3张
第1窗口售出了第 3 号票, 还剩下: 2张
第2窗口售出了第 2 号票, 还剩下: 1张
第1窗口售出了第 1 号票, 还剩下: 0张
售罄了......
售罄了......
推荐使用: 同步代码
* 1. 同步代码的锁对象由我们随意指定,同步函数的锁对象是固定的,不能由我们指定
* 2. 同步代码可以很方便控制被同步代码的范围,同步函数必须是整个函数的所有代码都同步了!
4. 死锁现象
死锁现象原因: 存在两个或两个以上的线程; 存在两个或者两个以上的共享资源。 没有方案解决,避免发生!!!
class DeadLock extends Thread{
public DeadLock(String name) {
super(name);
}
@Override
public void run() {
if("女侠".equals(Thread.currentThread().getName())) {
synchronized ("遥控器") { //字符串常量池(String Pool)这部分也在方法区中,String Pool是JVM实例全局共享的,全局只有一个
System.out.println(" 女侠--遥控器,---> 电池 ");
synchronized ("电池") {
System.out.println(" 女侠全都搞定 ");
}
}
}else if ("仙女".equals(Thread.currentThread().getName())){
synchronized ("电池") {
System.out.println(" 仙女--电池,---> 遥控器 ");
synchronized ("遥控器") {
System.out.println(" 仙女全都搞定 ");
}
}
}
}
}
public class test_1 extends Thread{
public static void main(String[] args) {
DeadLock person1 = new DeadLock("女侠");
DeadLock person2 = new DeadLock("仙女");
person1.start();
person2.start();
}
}
5. 线程通讯
线程通讯: 一个线程完成了自己的任务,要通知另外的一个线程去完成另外一个任务
wait(): // 等待, 如果线程执行了 wait 方法,那么该线程就会进入等待的状态,必须要被其他的线程调用notify方法才能唤醒 // 调用wait方法,就会释放锁对象
notify(): // 唤醒, 唤醒以锁对象为标识符的线程,唤醒等待的线程 ,先等待的线程一般会先唤醒。
notifyAll(): 唤醒线程池中所有的线程
注意事项:
1. wait方法 和 notify方法是属于object对象的; 锁对象是object对象
2. 两个方法必须要在同步块或者同步函数中才能使用; 因为需要锁对象调用,同步代码中有锁对象
3. 这两种方法需要由锁对象调用,同一个锁对象! 一个锁对象建立一个线程池
例子: 生产者和消费者
class Product{ // 产品
String name;
double price;
boolean flag = false; // 是否开始的标志
}
class Producter extends Thread{ // 生产者
Product p;
public Producter(Product p) {
this.p = p;
}
@Override
public void run() {
int i = 0;
for(int t= 0; t<100; t++) {
synchronized (p) { // 出现价格错乱???? 需要同步
if(p.flag ==false) { // 消费者线程已经结束,生产线程开始进行
if(i%2==0) {
p.name = "柠檬";
p.price = 66.6;
}
else {
p.name = "百香果";
p.price = 33.3;
}
System.out.println("生产者生产出了: " + p.name + " 价格: " + p.price);
p.flag = true;
p.notify(); // 生产完,唤醒正在睡眠的消费者线程
i++;
}else {
try {
p.wait(); // 消费者线程正在进行,生产线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Customer extends Thread{ // 消费者
Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
for(int t= 0; t<100; t++) {
synchronized (p) { // 出现价格错乱???? 需要同步
if(p.flag ==true) {
System.out.println(" 付祖贤减肥成功 "+ p.name + " " + p.price);
p.flag = false;
p.notify();
}else {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class test_1 {
public static void main(String[] args) {
Product p = new Product(); //
Producter producter = new Producter(p);
Customer customer = new Customer(p);
producter.start();
customer.start();
}
}
6. 线程的停止
1. 停止一个线程,一般通过一个变量去控制。
2. 停止一个等待状态的线程,需要用一个变量配合 notify() 或者 interrupt()
public class test_1 extends Thread{
boolean flag= true;
public test_1(String name) {
super(name);
}
@Override
public synchronized void run() {
int i = 0;
while(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": " + i);
i++;
}
}
public static void main(String[] args) {
test_1 tt = new test_1("付祖贤女侠");
tt.setPriority(10);
tt.start();
for(int i=0; i<6; i++) {
System.out.println(Thread.currentThread().getName()+ ": " + i);
if(i==3) {
// tt.stop(); //
// tt.interrupt(); // 无法停止线程
tt.flag=false;
tt.interrupt(); // 把等待状态的线程清除,被清除的线程会收到 一个异常 InterruptedException
/* synchronized (tt) {
tt.notify(); // 不能指定唤醒哪个线程
} */
}
}
}
}
************************** output ********************************
main: 0
main: 1
main: 2
main: 3
main: 4
main: 5
java.lang.InterruptedException
付祖贤女侠: 0
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at test.test_1.test_1.run(test_1.java:14)
7. 守护线程(后台线程)Daemon
守护线程(后台线程): 在一个进程中如果只剩下了守护线程,那么守护线程也会死亡。
一个线程都不会默认为守护线程。设置: setDaemon();
public class test_1 extends Thread {
public test_1(String name) {
super(name);
}
@Override
public void run() {
for(int i= 0; i<20;i++) {
System.out.println("更新包目前下载了" + i + "%");
if(i==20) {
System.out.println("更新包下载完毕");
}
try {
this.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
test_1 tt = new test_1("后台线程");
tt.setDaemon(true); // 怎样成为守护线程 ?? main 线程结束之后,守护线程也死亡!
System.out.println("是守护线程吗? " + tt.isDaemon()); // 怎样判别守护线程??
tt.start();
for(int i= 0; i<20;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
8. join 方法
join 方法 : 一个线程执行好了join()语句,就有新的线程执行,执行该语句的线程必须要让步给新加入的线程先完成任务,然后才能继续执行
class Washing extends Thread{ // washing类
@Override
public void run() {
System.out.println("付祖贤刷牙");
System.out.println("付祖贤洗脸");
System.out.println("付祖贤补水,发现喷雾没有了");
Spray spray = new Spray();
spray.start();
try {
spray.join(); // 加入, 由 washing类线程 来执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("付祖贤涂乳液");
System.out.println("付祖贤涂隔离霜");
System.out.println("付祖贤涂防晒霜");
}
}
class Spray extends Thread{ // 喷雾类
@Override
public void run() {
System.out.println("那就找屈臣氏的那瓶水");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("忘了那瓶水在哪?");
System.out.println("那瓶水在搁架上");
System.out.println("在搁架上找到了那瓶水");
}
}
public class test_1 {
public static void main(String[] args) {
Washing washing = new Washing();
washing.start();
}
}
************************** output **************************************
付祖贤刷牙
付祖贤洗脸
付祖贤补水,发现喷雾没有了
那就找屈臣氏的那瓶水
忘了那瓶水在哪?
那瓶水在搁架上
在搁架上找到了那瓶水
付祖贤涂乳液
付祖贤涂隔离霜
付祖贤涂防晒霜
//\\