程序、进程、线程的定义
每个线程都有一套虚拟机栈和程序计数器,而方法区和堆只有一个进程才有一套,所以方法区和堆是进程里面线程共享的,例如堆里面提供了一个实例变量每个线程都可以去修改。
多线程:
以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改
何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写
操作、网络操作、搜索等。 - 需要一些后台运行的程序时
多线程的创建
方式一:继承Thread类
- 多线程的创建:
- 1.创建一个继承于Thread类的子类
- 2.重写Thread类的run()→ 将此线程执行的操作声明在run()
- 3.创建Thread类的子类的对象
- 4.通过此对象调用start()
package com.acoffee.java;
/**
* 多线程的创建,方式一:继承Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run()→ 将此线程执行的操作声明在run()
* 3.创建Thread类的子类的对象
* 4.通过此对象调用start()
* @author acoffee
* @create 2020-09-24 16:46
*/
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
evenNmber t1 = new evenNmber();
//4.通过此对象调用start():
t1.start();
//如下操作仍然是在main线程中执行的
for (int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(i + "***main()***");
}
}
}
}
//1.创建一个继承于Thread类的子类
class evenNmber extends Thread {
//2.重写Thread类的run()→ 将此线程执行的操作声明在run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
使用匿名方法去调用线程
new Thread(){run方法体}.start();
package com.acoffee.exer;
/**
* 练习:创建两个分线程,其中一个线程遍历100以内的
* 偶数,另一个遍历100一类的奇数
* @author acoffee
* @create 2020-09-24 18:13
*/
public class ThreadTest {
public static void main(String[] args) {
// Mythread1 m1 = new Mythread1();
// Mythread2 m2 = new Mythread2();
//
// m2.start();
// m1.start();
//使用匿名的方法:创建Thread类的匿名子类的方式
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
//遍历100以内的偶数
class Mythread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
//遍历100以内的奇数
class Mythread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
方式二:实现Runnable接口
- 创建多线程的方式二:
- 1.创建一个实现了Runable接口的类
- 2.实现类去实现Runable中的抽象方法:run()
- 3.创建实现类的对象
- 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 5.通过Thread类的对象调用start()
package com.acoffee.java;
/**
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现了Runable接口的类
* 2.实现类去实现Runable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
* @author acoffee
* @create 2020-09-24 20:58
*/
//1.创建一个实现了Runable接口的类
class Mthread implements Runnable{
//2.实现类去实现Runable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
Mthread mthread = new Mthread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mthread);
t1.setName("线程一");
//5.通过Thread类的对象调用start():调用了Runnable类型的target(mthread)的run()
t1.start(); //谁start的谁就是线程
//在启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mthread);
t2.setName("线程二");
t2.start();
}
}
Thread类的有关方法
上述方法实现的代码:
package com.acoffee.exer;
/**
* @author acoffee
* @create 2020-09-24 18:56
*/
public class ThreadMethodTest {
public static void main(String[] args) {
//通过构造器给线程命名:
Mythread3 m1 = new Mythread3("thread:");
//setName就是name属性的set方法
//getName就是name属性的get方法
m1.setName("线程一");
m1.start();
//给主线程命名:
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i == 20) {
try {
//在线程A中调用线程B的join方法,此时线程A就进入阻塞状态
//直到线程B完全执行完以后,线程A才结束阻塞状态。
m1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(m1.isAlive());//判断线程是否存活,即是否运行完
}
}
class Mythread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
//这里是不能用throw的方法去抛异常的
//因为我们是继承Thread中的run方法的
//因为Thread类中都没有抛异常,子类抛
//的异常是不能大于父类的。
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i % 20 == 0){
//释放当前cpu的执行:相当于重新分配执行权,但是还是有可能分到这个线程
yield();
}
}
}
//命名的构造器
public Mythread3(String name){
super(name);
}
}
线程优先级:
高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有高优先级的线程执行完后,低优先级才执行。
//获取优先级
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
//设置优先级
m1.setPriority(Thread.MAX_PRIORITY);
练习:
方式一实现:
package com.acoffee.java;
/**
* 例子:创建三个窗口买票,总票数为100张
* @author acoffee
* @create 2020-09-24 20:35
*/
class window extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
if(ticket > 0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
window w1 = new window();
window w2 = new window();
window w3 = new window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
执行结果:
从执行结果我们可以看到此方法存在线程不安全的情况。我们在同步处在进行处理。
方式二实现:
package com.acoffee.java;
/**
* @author acoffee
* @create 2020-09-24 21:25
*/
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket = 100;//这里不用加static因为这里只创建了一个window对象
@Override
public void run() {
while (true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else{
break;
}
}
}
}
比较创建线程的两种方式:
开发中,优先选择:实现Runnable接口的方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run( ),将线程要执行的逻辑声明在run( )中。
目前这两种方式,要想启动线程,都是调用的Thread类中的start( )。
每日一练:
1. 谈谈你对程序、进程、线程的理解
程序(program):即指一 段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。 Java中的线程分为两类:一种是守护线程,一种是用户线程。
2. 代码完成继承Thread的方式创建分线程,并遍历100以内的自然数
class ThreadTest {
public static void main(String[] args){
thread1 t1 = new thread1();
t1.start();
}
}
class thread1 extends Thread{
public void run(){
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
3. 代码完成实现Runnable接口的方法创建分线程,并遍历100以内的自然数
class ThreadTest {
public static void main(String[] args){
//创建实现类的对象
thread2 t1 = new thread2();
//传入Thread构造器
Thread t2 = new Thread(t1);
t2.start();
}
}
class thread2 implements Runnable{
public void run(){
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
4. 对比两种创建方式
开发中,优先选择:实现Runnable接口的方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run( ),将线程要执行的逻辑声明在run( )中。
5. 说说你对IDEA中Project和Module的理解
Project是IDEA中最高层级的,一个Project中可以有多个Module
eclipse中的workspace就相当于IDEA中的Project,eclipse中的project就相当于IDEA中的Module