多线程和网络编程(1)
一、多线程
1、进程和线程
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序。
多线程:一个进程如果有多条执行路径,则成为多线程程序。
2、多线程的实现方式
方式1:继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
案例:
/**
* @Author TeaBowl
* @Date 2021/3/16 17:27
* @Version 1.0
* 定义一个类MyThread继承Thread类
*/
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("i:" + i);
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/16 17:24
* @Version 1.0
* 方式1:继承Thread类
*/
public class MyThreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
控制台:
小问题:
1.为什么要重写run()方法?
因为run()方法是用来封装被线程执行的代码。
2.run()方法和start()方法的区别?
run():封装被线程执行的代码,直接调用,相当于普通方法的调用。
start():启动线程,然后油JVM调用此线程的run()方法。
设置和获取线程名称
Thread类中设置和获取线程名称的方法
- void setName(String name):将此线程的名称更改为等于参数name。
- String getName():返回线程的名称。
- 通过构造方法也可以设置线程名称。
案例1:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("高铁");
m2.setName("飞机");
m1.start();
m2.start();
}
}
控制台:
案例2:
public class MyThread extends Thread {
//无参构造
public MyThread(){
}
//带参构造
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
//Thread(String name)
MyThread m1 = new MyThread("高铁");
MyThread m2 = new MyThread("飞机");
m1.start();
m2.start();
}
}
控制台:
小问题:如何获取main()方法所在的线程名称?
public class MyThreadDemo {
public static void main(String[] args) {
//currentThread():返回正在执行的线程对象的引用,静态方法。
System.out.println(Thread.currentThread().getName());
}
}
控制台输出:main
线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间片。
- 优先让优先级高的线程使用CPU,如果线程优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
线程优先级:最小1、默认5、最大10。
线程优先级高,并不是指每次都跑在最前面,而是指获得CPU时间片几率高。
java使用的是抢占式调度模型
Thread类中设置和获取线程优先级的方法
- public final int getPriority():返回此线程的优先级
- Public final setPriority(int newPriority):更改此线程的优先级
案例一:
public class MyThread extends Thread {
//无参构造
public MyThread(){
}
//带参构造
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
//Thread(String name)
MyThread m1 = new MyThread("高铁");
MyThread m2 = new MyThread("飞机");
MyThread m3 = new MyThread("汽车");
//public final int getPriority():返回此线程的优先级
//线程默认优先级:5
System.out.println(m1.getPriority());
System.out.println(m2.getPriority());
System.out.println(m3.getPriority());
}
}
控制台:
案例二:
public class MyThreadDemo {
public static void main(String[] args) {
//Thread(String name)
MyThread m1 = new MyThread("高铁");
MyThread m2 = new MyThread("飞机");
MyThread m3 = new MyThread("汽车");
//Public final setPriority(int newPriority):更改此线程的优先级
//IllegalArgumentException:非法参数异常(线程优先级不在允许的范围内)
//线程优先级范围(1-10)
m1.setPriority(1);
m2.setPriority(6);
m3.setPriority(4);
m1.start();
m2.start();
m3.start();
//public final int getPriority():返回此线程的优先级
//线程默认优先级:5
System.out.println(m1.getPriority());
System.out.println(m2.getPriority());
System.out.println(m3.getPriority());
//线程优先级:最小、默认、最大
System.out.println(Thread.MIN_PRIORITY);
System.out.println(Thread.NORM_PRIORITY);
System.out.println(Thread.MAX_PRIORITY);
}
}
控制台:
结论:线程优先级高,并不是指每次都跑在最前面,而是指获得CPU时间片几率高。
线程控制
sleep案例:
/**
* @Author TeaBowl
* @Date 2021/3/18 12:32
* @Version 1.0
*/
public class ThreadSleep extends Thread{
//无参构造
public ThreadSleep(){
}
//带参构造
public ThreadSleep(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
try {
//每执行一次输出,线程休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/18 12:33
* @Version 1.0
* static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep t1 = new ThreadSleep("曹操");
ThreadSleep t2 = new ThreadSleep("刘备");
ThreadSleep t3 = new ThreadSleep("孙权");
//3个线程同时执行,每秒休眠一次
t1.start();
t2.start();
t3.start();
}
}
控制台:
join案例:
public class ThreadSleep extends Thread{
//无参构造
public ThreadSleep(){
}
//带参构造
public ThreadSleep(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
/*try {
//每执行一次输出,线程休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/18 12:33
* @Version 1.0
* void join():等待当前线程死亡
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep t1 = new ThreadSleep("曹操");
ThreadSleep t2 = new ThreadSleep("刘备");
ThreadSleep t3 = new ThreadSleep("孙权");
t1.start();
try {
//t1线程死亡之后,其他线程才有可能执行
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
控制台:
setDeamon案例:
/**
* @Author TeaBowl
* @Date 2021/3/18 12:32
* @Version 1.0
*/
public class ThreadSleep extends Thread{
//无参构造
public ThreadSleep(){
}
//带参构造
public ThreadSleep(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/18 12:33
* @Version 1.0
* void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,java虚拟机将退出
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep t1 = new ThreadSleep("关羽");
ThreadSleep t2 = new ThreadSleep("张飞");
//设置主线程为刘备
//currentThread():返回正在执行的线程对象的引用,静态方法。
Thread.currentThread().setName("刘备");
//设置关羽和张飞为刘备的守护线程
// 如果刘备挂了,守护线程很快也得挂
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
//输出线程刘备
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
控制台:
线程生命周期
方式2:实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口。
- 在MyRunnable类中重写run()方法。
- 创建MyRunnable类的对象。
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数。
- 启动线程。
案例:
/**
* @Author TeaBowl
* @Date 2021/3/18 16:36
* @Version 1.0
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/18 16:38
* @Version 1.0
*/
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数。
//Thread(Runnable r)
//Thread(Runnable r,String name):给线程设置名称
Thread t1 = new Thread(myRunnable,"李白");
Thread t2 = new Thread(myRunnable,"苏烈");
//启动线程
t1.start();
t2.start();
}
}
控制台:
相比继承Thread类,实现Runnable接口的好处
- 避免了java单继承的局限性
- 适合多个相同程序代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
线程同步
案例:卖票
/**
* @Author TeaBowl
* @Date 2021/3/24 14:30
* @Version 1.0
* 要执行的任务
*/
public class SellTicket implements Runnable{
//是设置票数为100
private int tickets = 100;
@Override
public void run() {
//循环执行卖票
while (true){
//票数>0时,就卖票
if (tickets>0){
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
//出售之后,总票数-1
tickets--;
}
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/24 14:26
* @Version 1.0
*/
public class SellTicketDemo {
public static void main(String[] args) {
//创建多线程对象
SellTicket st = new SellTicket();
//创建三个线程,把多线程对象作为参数,并定义线程名称
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
控制台:
如图,出现了几个问题。
1.重复卖票
2.漏卖(卖出第100张接着却是卖第97张)
3.负数票(第-1张)
分析:
卖票案例数据安全问题的解决。
同步代码块。
重复代码块加锁(同一把锁)。
@Override
public void run() {
//循环执行卖票
while (true){
//重复代码块加锁(同一把锁)
synchronized (obj){
//票数>0时,就卖票
if (tickets>0){
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
//出售之后,总票数-1
tickets--;
}
}
}
}
控制台输出正常。
窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票
同步代码块锁的分析
同步方法
案例
/**
* @Author TeaBowl
* @Date 2021/3/24 14:30
* @Version 1.0
*/
public class SellTicket implements Runnable {
private static int tickets = 100;
//private Object obj = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
synchronized (SellTicket.class) {
//票数>0时,就卖票
if (tickets > 0) {
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
//出售之后,总票数-1
tickets--;
}
}
} else {
sellTiket();
}
x++;
}
}
//同步方法
private static synchronized void sellTiket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/24 14:26
* @Version 1.0
*/
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
控制台输出正常:
窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票
线程安全的类
线程不安全的集合类转为线程安全的集合类
/**
* @Author TeaBowl
* @Date 2021/3/24 16:45
* @Version 1.0
*/
public class ThreadDemo {
public static void main(String[] args) {
//static <T> List<T> synchronizedList(List<T> list):返回由指定列表支持的同步(线程安全)列表
List<String> list = Collections.synchronizedList(new ArrayList<>());
}
}
Lock锁
案例
/**
* @Author TeaBowl
* @Date 2021/3/24 14:30
* @Version 1.0
*/
public class SellTicket implements Runnable {
private static int tickets = 100;
//Lock是个接口,使用它的无参构造方法ReentrantLock()创建Lock锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}finally {
lock.unlock();
}
}
}
}
/**
* @Author TeaBowl
* @Date 2021/3/24 14:26
* @Version 1.0
*/
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
控制台:
窗口1正在出售第100张票
窗口1正在出售第99张票
窗口1正在出售第98张票
窗口1正在出售第97张票
……
窗口1正在出售第5张票
窗口1正在出售第4张票
窗口1正在出售第3张票
窗口1正在出售第2张票
窗口1正在出售第1张票
生产者和消费者模式
生产者和消费者案例:
生产者类:
/**
* @Author TeaBowl
* @Date 2021/3/24 20:22
* @Version 1.0
* 生产者类:送奶工
*/
public class Producer implements Runnable {
//定义成员变量:牛奶箱
private Box box;
//定义带参构造方法
public Producer(Box box) {
this.box = box;
}
//生产者执行的操作:生产5瓶牛奶
//i要传给奶箱类Box里面的milk
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
box.put(i);
}
}
}
消费者类:
/**
* @Author TeaBowl
* @Date 2021/3/24 20:23
* @Version 1.0
* 消费者类:用户
*/
public class Customer implements Runnable {
//创建成员变量:牛奶箱
private Box box;
//创建带参构造方法
public Customer(Box box) {
this.box = box;
}
//消费者执行的操作
@Override
public void run() {
while (true){
box.get();
}
}
}
奶箱类:
/**
* @Author TeaBowl
* @Date 2021/3/24 20:16
* @Version 1.0
* 奶箱类
*/
public class Box {
//定义变量:用来表示第几瓶牛奶
private int milk;
//定义成员变量,表示牛奶箱的状态
private Boolean state = false;
//生产者操作
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if (state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶,就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶牛奶放入牛奶箱");
//生产完之后,更改奶箱状态
state = true;
//唤醒其他等待的线程
notifyAll();
}
//消费者操作
public synchronized void get() {
//如果没有牛奶,等待生产
if (!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶,消费牛奶
System.out.println("用户拿到第" + this.milk + "瓶牛奶");
//消费完之后,更改奶箱状态
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
测试类:
/**
* @Author TeaBowl
* @Date 2021/3/24 20:15
* @Version 1.0
* 生产者和消费者案例,测试类
*/
public class BoxDemo {
public static void main(String[] args) {
//创建奶箱对象
Box box = new Box();
//创建生产者对象,参数:牛奶箱
Producer producer = new Producer(box);
//创建消费者对象,参数:牛奶箱
Customer customer = new Customer(box);
//创建生产者线程
Thread t1 = new Thread(producer);
//创建消费者线程
Thread t2 = new Thread(customer);
//启动线程
t1.start();
t2.start();
}
}
控制台:
送奶工将第1瓶牛奶放入牛奶箱
用户拿到第1瓶牛奶
送奶工将第2瓶牛奶放入牛奶箱
用户拿到第2瓶牛奶
送奶工将第3瓶牛奶放入牛奶箱
用户拿到第3瓶牛奶
送奶工将第4瓶牛奶放入牛奶箱
用户拿到第4瓶牛奶
送奶工将第5瓶牛奶放入牛奶箱
用户拿到第5瓶牛奶