理解线程

进程是指一个内存中运行的应用程序,系统运行一个程序即是一个进程从创建,运行,结束的过程。

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。

多线程的特点是并发执行(同一时间段执行多个任务),实际上并不能提高程序运行速度,但能够提高运行效率,让cpu使用率更高。

关于线程调度,分为分时调度和抢占调度。

  • 抢占调度模式,需要设置线程的优先级,优先级别高的线程优先使用cpu。
  • 分时调度,所有线程轮流使用cpu,平均分配每个线程占用cpu的时间。

1.Java中的多线程实现

Java中实现多线程有两种方式:

  1. 继承Thread类并重写run方法,创建Thread对象执行。
  2. 定义Runable接口的实现类,并重写接口的run方法(线程执行体)。Runnable实现类里包含的run()方法仅作为线程执行体,然后传递给Thread对象执行。

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

构造方法:

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

下面通过代码来了解执行过程:

//继承Thead实现
class MyThead extends Thread{
public MyThead(String name){
super(name);
}
@Override
public void run(){
System.out.println("我是线程"+getName());
}
} public class TestThread {
public static void main(String[] args){
//创建线程对象
MyThead mt = new MyThead("M");
//开启一个新的线程
mt.start();
System.out.println("我是main线程");
}
}
//实现Runnable,重写run方法
class MyRunable implements Runnable{
@Override
public void run(){
System.out.println("我是线程"+ Thread.currentThread().getName());
}
}
public class TestRunable {
public static void main(String[] args){
//创建 MyRunable对象
MyRunable mr = new MyRunable();
//创建线程对象
Thread t = new Thread(mr,"t");
//运行线程
t.start();
System.out.println("我是main线程");
}
}

2.线程同步

使用多个线程访问同一资源的时候,并在线程中修改资源,就会出现线程安全问题。

//实现Runnable,重写run方法
class MyRunable implements Runnable{
private int a = 10;
@Override
public void run(){
while (a > 0){ try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
a -= 1;
System.out.println("线程"+Thread.currentThread().getName()+"操作了a,a的值为"+a);
} }
}
public class TestRunable {
public static void main(String[] args){
//创建 MyRunable对象
MyRunable mr = new MyRunable();
//创建线程对象
Thread t1 = new Thread(mr,"t1");
Thread t2 = new Thread(mr,"t2");
Thread t3 = new Thread(mr,"t3");
//运行线程
t1.start();
t2.start();
t3.start(); }
}

由于多个线程操作变量a,导致a的值异常(-1不应该出现)。

线程安全问题都是由于多线程对全局变量进行修改操作引起的,这时就需要使用线程同步。

线程同步原理:

在一个线程操作资源时,其他线程不允许操作,保证数据的同步性。

Java中提供了三种方式来实现线程同步。

  • 同步代码块:synchronized 关键字可以用于方法中的某个区块中,为区块代码添加互斥访问。
  • 同步方法 :使用synchronized修饰方法。一个线程在执行该方法时,其他线程阻塞。

  • Lock 锁:也称同步锁,加锁与释放锁方法化了。

同步代码块:

//实现Runnable,重写run方法
class MyRunable implements Runnable{
private int a = 10;
//同步锁
Object lock = new Object();
@Override
public void run(){
while (true){
synchronized (lock){
if (a > 0){
try{
Thread.sleep(50);
}catch (InterruptedException e){
e.printStackTrace();
}
a -= 1;
System.out.println("线程"+Thread.currentThread().getName()+"操作了a,a的值为"+a); } }
}
}
}
public class TestRunable {
public static void main(String[] args){
//创建 MyRunable对象
MyRunable mr = new MyRunable();
//创建线程对象
Thread t1 = new Thread(mr,"t1");
Thread t2 = new Thread(mr,"t2");
Thread t3 = new Thread(mr,"t3");
//运行线程
t1.start();
t2.start();
t3.start(); }
}

同步方法:

//实现Runnable,重写run方法
class MyRunable implements Runnable{
private int a = 10;
@Override
public void run(){
while (true) {
method1();
}
}
//同步方法
public synchronized void method1(){
if (a > 0){
try{
Thread.sleep(50);
}catch (InterruptedException e){
e.printStackTrace();
}
a -= 1;
System.out.println("线程"+Thread.currentThread().getName()+"操作了a,a的值为"+a); }
}
}
public class TestRunable {
public static void main(String[] args){
//创建 MyRunable对象
MyRunable mr = new MyRunable();
//创建线程对象
Thread t1 = new Thread(mr,"t1");
Thread t2 = new Thread(mr,"t2");
Thread t3 = new Thread(mr,"t3");
//运行线程
t1.start();
t2.start();
t3.start(); }
}

Lock锁包含两个方法:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; //实现Runnable,重写run方法
class MyRunable implements Runnable{
private int a = 10;
//同步锁
Lock lock = new ReentrantLock();
@Override
public void run(){
while (true){
//加锁
lock.lock();
if (a > 0){
try{
Thread.sleep(20);
}catch (InterruptedException e){
e.printStackTrace();
}
a -= 1;
System.out.println("线程"+Thread.currentThread().getName()+"操作了a,a的值为"+a); }
//释放锁
lock.unlock();
}
}
}
public class TestRunable {
public static void main(String[] args){
//创建 MyRunable对象
MyRunable mr = new MyRunable();
//创建线程对象
Thread t1 = new Thread(mr,"t1");
Thread t2 = new Thread(mr,"t2");
Thread t3 = new Thread(mr,"t3");
//运行线程
t1.start();
t2.start();
t3.start(); }
}

3.线程状态

  • NEW:线程创建未启动。
  • Runnable:可运行。
  • Blocked:锁阻塞。当一个线程获得同步锁时,其他线程会被阻塞,直到获得同步锁。
  • Waiting:无限等待。当一个线程进入Waiting状态后,会无限等待,直到其他线程调用notify或者notifyAll方法唤醒(线程通信)。
  • TimedWaiting:计时等待。当一个线程调用sleep方法时,程序会进入计时等待,直到时间结束。
  • Teminated:被终止。run方法未正常退出而死亡。

4.线程通信

多个线程在处理同一资源,但是线程任务却不相同,当我们希望有规律的执行吗,那么线程之间需要一些协调通信。

线程通信的核心是等待唤醒机制。等待唤醒机制是多线程间的一种协作机制,就是一个线程执行wait操作进入无限等待状态,等待其他线程执行 notify 唤醒线程,或者使用 notifyAll 来唤醒所有的等待线程 。

等待唤醒方法:

  • wait:线程进入wait状态,不会去参与调度。
  • notify:释放所通知对象的一个线程。
  • nofifyAll:释放所通知对象的全部线程。

唤醒方法使用注意:

  • wait 和 notify 方法必须要由同一个所对象调用。
  • 两个方法都是属于object类的方法。
  • 两个方法必须要在同步代码块或同步函数中。因为必须要通过锁对象调用这两个方法。

案例:生产者与消费者模型。

class BaoZi {
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
//把包子当做同步锁对象
synchronized (bz){
if(bz.flag == false){//没包子
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃包子");
bz.flag = false;
System.out.println("吃完了");
bz.notify();
}
}
}
}
class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz = bz;
} @
Override
public void run() {
int count = 0;
while(true){
synchronized (bz){
if(bz.flag == true){//包子资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子铺开始做包子");
count++;
bz.flag=true;
System.out.println("包子造好了");
System.out.println("吃货来吃吧");
//唤醒线程
bz.notify();
}
}
}
} public class TestRunable {
public static void main(String[] args){
//创建包子对象
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
}
}

5.线程池

线程池可以容纳多个线程,用于解决频繁创建线程和销毁线程的资源消耗,使线程可以被重复使用。

合理使用线程池的好处:

  • 降低资源消耗。无需频繁创建线程和销毁。
  • 提高响应速度。任务到达不需要等待线程创建就可以立即执行。
  • 提高线程的可管理性。根据系统的承受能力,配置合适的线程数量。

Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。

java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。(官方建议使用)

方法:

  • public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。
  • public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行 。

使用步骤:

  1. 创建线程池对象。
  2. 创建Runable接口子类对象。
  3. 获得线程池对象,执行。
  4. 关闭线程池。(一般不做)

代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用了"+Thread.currentThread().getName());
}
}
public class TestRunable {
public static void main(String[] args){
//创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);
//创建 Runable 子类对象
MyRunnable r = new MyRunnable();
//获取线程对象 调用run
for (int i =0;i<10;i++){
service.submit(r);
} }
}

温馨提示

  • 如果您对本文有疑问,请在评论部分留言,我会在最短时间回复。
  • 如果本文帮助了您,也请评论关注,作为对我的一份鼓励。
  • 如果您感觉我写的有问题,也请批评指正,我会尽量修改。

Java基础学习(八) - 多线程的更多相关文章

  1. Java基础学习总结 -- 多线程的实现

    目录: 继承Thread类 start()方法实现多线程的原理 实现Runnable接口 Thread类 与 Runnable接口 的联系与区别 多线程的实现方法: 继承Thread类 实现Runna ...

  2. Java基础学习篇---------多线程

    一.编写两种多线程的方法 (1).Thread(它是继承Runnable的子类) class MyThread extends Thread{ private int ticket = 5; @Ove ...

  3. Java基础学习笔记总结

    Java基础学习笔记一 Java介绍 Java基础学习笔记二 Java基础语法之变量.数据类型 Java基础学习笔记三 Java基础语法之流程控制语句.循环 Java基础学习笔记四 Java基础语法之 ...

  4. 尚学堂JAVA基础学习笔记

    目录 尚学堂JAVA基础学习笔记 写在前面 第1章 JAVA入门 第2章 数据类型和运算符 第3章 控制语句 第4章 Java面向对象基础 1. 面向对象基础 2. 面向对象的内存分析 3. 构造方法 ...

  5. JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API

    森林森 一份耕耘,一份收获 博客园 首页 新随笔 联系 管理 订阅 随笔- 397  文章- 0  评论- 78  JAVA基础学习day16--集合三-Map.HashMap,TreeMap与常用A ...

  6. Java基础学习-- 继承 的简单总结

    代码参考:Java基础学习小记--多态 为什么要引入继承? 还是做一个媒体库,里面可以放CD,可以放DVD.如果把CD和DVD做成两个没有联系的类的话,那么在管理这个媒体库的时候,要单独做一个添加CD ...

  7. Java基础学习中一些词语和语句的使用

    在Java基础学习中,我们刚接触Java会遇到一些词和语句的使用不清的情况,不能很清楚的理解它的运行效果会是怎么样的,如:break,continue在程序中运行效果及跳转位置, 1.先来看看brea ...

  8. 转载-java基础学习汇总

    共2页: 1 2 下一页  Java制作证书的工具keytool用法总结 孤傲苍狼 2014-06-24 11:03 阅读:25751 评论:3     Java基础学习总结——Java对象的序列化和 ...

  9. java基础学习总结——开篇

    java是我学习的第一门编程语言,当初学习java基础的时候下了不少功夫,趁着这段时间找工作之际,好好整理一下以前学习java基础时记录的笔记,当作是对java基础学习的一个总结吧,将每一个java的 ...

随机推荐

  1. codeforces 821 D. Okabe and City(最短路)

    题目链接:http://codeforces.com/contest/821/problem/D 题意:n*m地图,有k个位置是点亮的,有4个移动方向,每次可以移动到相邻的点亮位置,每次站在初始被点亮 ...

  2. poj 2763 Housewife Wind(树链剖分+单点查询+区间修改)

    题目链接:http://poj.org/problem?id=2763 题意:给一个数,边之间有权值,然后两种操作,第一种:求任意两点的权值和,第二,修改树上两点的权值. 题解:简单的树链剖分. #i ...

  3. NOIP 2005 等价表达式 题解

    题意 给一个表达式然后再给n个表达式,判断是否等价 一道大模拟题,将a带为数,并且取模防止溢出 #include<bits/stdc++.h> using namespace std; c ...

  4. Linux下设置path

    1.直接用export命令: #export PATH=$PATH:自定义路径 查看是否已经设好,可用命令export查看: 2.修改profile文件: #vi /etc/profile 在里面加入 ...

  5. 对line-height的理解

    <html> <head> <style> </style> </head> <body> <!--line-height ...

  6. java学习笔记之基础篇

    java选择语句之switch   //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...

  7. jumper-server-部署官网版

    本文链接:https://www.cnblogs.com/wwtao/p/11491574.html 官网链接: https://jumpserver.readthedocs.io/zh/master ...

  8. 安装Harbor管理镜像服务

    Harbor是什么? 还记得Docker Registry么?它是Docker官方提供的镜像仓库,简单易用,一键就可以部署.使用. 虽然看起来不错,但是Registry有些问题需要解决: 没有图形界面 ...

  9. java中的equals方法与"=="运算符解说

    在编程的时候,我们可能经常会用到equals方法,那么这个equals方法和运算符“==”有什么样的区别? public class equal { /** * @param args */ publ ...

  10. NUMA导致的MySQL服务器SWAP问题分析

    [作者] 王栋:携程技术保障中心数据库专家,对数据库疑难问题的排查和数据库自动化智能化运维工具的开发有强烈的兴趣. [问题描述] 我们知道当mysqld进程使用到SWAP时,就会严重影响到MySQL的 ...